一文掌握多线程并发中 Thread 类 yield 方法具体作用

简介: 一文掌握多线程并发中 Thread 类 yield 方法具体作用

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3NzgxNjQ5,size_16,color_FFFFFF,t_70#pic_center

每日一言

真爱的第一个征兆,在男孩身上是 胆怯,在女孩身上是 大胆。——雨果《悲惨世界》

1. Thread.yield() 是什么


通过 java.lang.Thread 类中的 yield() 方法可以实现让当前正在执行的线程让出 CPU 时间片

线程状态 Running(运行中) 执行后会变为 Ready(就绪) 状态

此时其它处于 Ready 状态 的线程可能获取到 CPU 时间片,也有可能是调用 yield() 方法的线程再次获得

2. 线程状态


我们查看下线程 Thread 中 State 枚举中代表线程状态的值

public enum State {
    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING,
    
    TIMED_WAITING,

    TERMINATED;
}

2.1 NEW

表示刚创建的线程,还没有调用 Thread.start() ,这种线程还没开始执行

Thread thread = new Thread();

2.2 RUNNABLE

当线程调用了 Thread.start() ,表示线程进入了可运行状态,即表示 Running 状态 & Ready 状态

Thread thread = new Thread();
thread.start();

可运行不代表运行状态,可以理解为 Ready 就绪状态,等待获取到 CPU 时间片,才会变为 Running 运行状态

2.3 BLOCKED

在线程执行中,没有获得 synchronized 块/方法 & 进入 synchronized 块/方法后调用 wait() 被唤醒/超时后重新进入 synchronized 块/方法 ,会进入阻塞状态

阻塞状态的线程会暂定执行,不占用 CPU 时间片资源,直到获取锁

进入 BLOCKED 状态的线程,不会感知到 interrupt() 中断方法

2.4 WAITING、TIMED_WAITING

两种状态都表示等待状态,不同的是 WAITING 是无限期等待,需要另一个事件进行唤醒;TIMED_WAITING 等到一个时限点时自我唤醒

WAITING

Object.wait();
Thread.join();
LockSupport.park();

如果没有 被唤醒或等待的线程没有结束,那么将一直等待,当前状态的线程 不会被分配CPU资源和持有锁

TIMED_WAITING

Thread.sleep(long);
Object.wait(long);
Thread.join(long);
LockSupport.parkNanos(long);
LockSupport.parkUntil(long);

2.5 TERMINATED

当线程执行完毕后,会进入 TERMINATED 终止、结束的状态

2.6 注意⚠️

NEW 状态执行后,线程不能再回到 NEW 状态,同样,处于 TERMINATED 的线程也不能再回到 RUNNABLE 状态

3. 源码声明


首先来看一下源码中 yield() 的方法声明

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *
 * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *
 * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
 */
public static native void yield();

从上述描述和方法声明中可以获取以下几点信息:

1、yield() 是 Thread 类的静态方法,同时也是 native 修饰的本地方法,无返回值

2、调度程序提示 当前线程愿意让出当前使用的处理器,但是调度程序可能 随意忽略此提示

3、正常情况下很少使用此方法。对于调试或测试目的,它可能是有用的,在某些情况下,它可能由于 竞争条件而有助于重现错误

4、当设计并发控制程序时,尽量使用 JUCL 包下的程序

3.1 小结一下

注释告诉我们,yield() 只是向调度器发起让出 CPU 的请求,但是 调度器可能不鸟你

应该谨慎使用 yield(),如果出于测试的目的可以使用来复现错误,如果设计并发控制程序,还是要用 JUCK 包下的程序

4. 小程序测试释放 CPU


下面这段小程序的执行目的是为了 验证 yield() 方法释放线程 CPU 后可能会再次获取到 CPU

1、创建两个线程,分别是 麻花 & 百万

2、每个线程 for 循环 40 次,每执行 10 次会执行 Thread.yield()

3、正常情况下会出现两种情况,分别是:

  • 执行到符合 % 运算条件的线程 下一个执行是自己本身
  • 执行到符合 % 运算条件的线程 下一个执行是另外一个线程

大家可以跑一下程序自己试试,结合实际情况理解 yield() 语义

public class YieldTest {
    public static void main(String[] args) {
        int max = 40;
        Runnable runnable = () -> {
            for (int i = 0; i <= max; i++) {
                System.out.println(
                        String.format("当前线程 :: %s, 执行进度 :: %d ", Thread.currentThread().getName(), i));
                if (i % 10 == 0) Thread.yield();
            }
        };
        new Thread(runnable, "百万").start();
        new Thread(runnable, "麻花").start();
    }
}

5. 面试题:yield() 是否释放 synchronized 锁


以一道面试题作为切入口:

多个线程竞争 synchronized 同步块 执行权时,获得执行权线程在同步块内部执行 yield(),是否会释放锁?

答:不会释放锁

我们还是以实际的小程序来演绎这个问题的过程,先来说下执行过程

1、创建两个线程,分别是 麻花 & 百万

2、“麻花” 启动首先获取锁,睡眠 50 毫秒等待 “百万” 竞争锁

3、“百万” 在 “麻花” 启动后,等待 10ms 后执行启动步骤,“百万” 竞争失败进入 阻塞状态

4、“麻花” 睡眠 50 ms 后,执行 Thread.yield() 释放 CPU

5、“麻花” 再次获取 CPU 时间片打印日志后释放锁,“百万” 获得锁

public class YieldTest {
    
    @SneakyThrows
    public static void main(String[] args) {
        Object lock = new Object();

        new Thread(() -> {
            synchronized (lock) {
                System.out.println(
                        String.format("当前线程 :: %s, 获取锁并准备执行 Thread.yield() ", Thread.currentThread().getName())
                );
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // ignore
                }
                Thread.yield();
                System.out.println(
                        String.format("当前线程 :: %s, 执行 Thread.yield() 后", Thread.currentThread().getName())
                );
            }
        }, "麻花").start();
        Thread.sleep(10);

        new Thread(() -> {
            System.out.println(
                    String.format("当前线程 :: %s, 开始竞争锁 ", Thread.currentThread().getName())
            );
            synchronized (lock) {
                System.out.println(
                        String.format("当前线程 :: %s, 获取锁 ", Thread.currentThread().getName())
                );
            }
        }, "百万").start();
    }
}

查看执行结果与上文答案相呼应,在 “麻花” 执行 Thread.yield() 后,“百万” 并没有直接获取锁,而是在 “麻花” 执行完同步块后获取到的锁

/**
 * 当前线程 :: 麻花, 获取锁并准备执行 Thread.yield() 
 * 当前线程 :: 百万, 开始竞争锁 
 * 当前线程 :: 麻花, 执行 Thread.yield() 后
 * 当前线程 :: 百万, 获取锁 
 */
相关文章
|
7月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
398 83
|
4月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
267 2
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
247 2
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
261 2
|
7月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
422 83
|
5月前
|
存储 Oracle Java
|
7月前
|
Java
创建线程的方法
Java中实现多线程有四种方式:1. 继承Thread类,简单但占用继承机会,耦合度高;2. 实现Runnable接口,推荐方式,任务与线程解耦,支持Lambda;3. 实现Callable接口配合FutureTask,可获取返回值和异常;4. 使用线程池(ExecutorService),企业推荐,管理线程生命周期,提升性能,支持多种线程池类型。
164 1
|
8月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
564 5
|
4月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
214 6

热门文章

最新文章