一文掌握多线程并发中 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() 后
 * 当前线程 :: 百万, 获取锁 
 */
相关文章
|
1月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
190 59
|
25天前
|
Java C# Python
线程等待(Thread Sleep)
线程等待是多线程编程中的一种同步机制,通过暂停当前线程的执行,让出CPU时间给其他线程。常用于需要程序暂停或等待其他线程完成操作的场景。不同语言中实现方式各异,如Java的`Thread.sleep(1000)`、C#的`Thread.Sleep(1000)`和Python的`time.sleep(1)`。使用时需注意避免死锁,并考虑其对程序响应性的影响。
|
26天前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
35 6
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
52 6
|
29天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
23 2
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
51 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
38 2