当前位置 博文首页 > Shockang的博客:线程的等待线程结束(join)和谦让(yield)是什么

    Shockang的博客:线程的等待线程结束(join)和谦让(yield)是什么

    作者:[db:作者] 时间:2021-08-24 09:54

    前言

    本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

    本专栏目录结构和参考文献请见100个问题搞定Java并发

    正文

    为什么需要 join?

    在很多情况下,线程之间的协作和人与人之间的协作非常类似。

    一种非常常见的合作方式就是分工合作。

    以我们非常熟悉的软件开发为例,在一个项目进行时,总是应该有几位号称是“需求分析师”的同事,先对系统的需求和功能点进行整理和总结,以书面形式给出份需求说明或者类似的参考文档,然后,软件设计师、研发工程师オ会一拥而上,进行软件开发。

    如果缺少需求分析师的工作输出,那么软件研发的难度可能会比较大。

    因此,作为名软件研发人员,总是喜欢等待需求分析师完成他应该完成的任务后,才愿意投身工作。

    简单地说,就是研发人员需要等待需求分析师完成他的工作,然后才能进行研发。

    将这个关系对应到多线程应用中,很多时候,一个线程的输入可能非常依赖于另外个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行

    JDK 提供了 join() 操作来实现这个功能。

    join 是什么?

    如下所示,显示了两个 join ()方法:

    public final void join()throws InterruptedException
    
    public final synchronized void join(long millis)throws InterruptedException
    

    第一个 join ()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。

    第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。

    英文 join 的翻译,通常是加入的意思。

    这个意思在这里也非常贴切。

    因为一个线程要加入另外一个线程,最好的方法就是等着它一起走

    join() 方法的本质是让调用线程 wait() 方法在当前线程对象实例上。(详情请见下面的源码解读)

    yield 是什么?

    另外一个比较有趣的方法是 Thread.yield ,它的定义如下:

    public static native void yield();
    

    这是一个静态方法,一旦执行,它会使当前线程让出 CPU 。

    但要注意,让出 CPU 并不表示当前线程不执行了。

    当前线程在让出 CPU 后,还会进行 CPU 资源的争夺,但是是否能够再次被分配到就不一定了。

    因此,对 Thread.yield 方法的调用就好像是在说:“我已经完成了一些最重要的工作了,我可以休息一下了,可以给其他线程一些工作机会啦!”

    如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的 CPU 资源,那么可以在适当的时候调用 Thread.yield 方法,给予其他重要线程更多的工作机会。

    源码(JDK8)

    /**
     * 等待此线程死亡。 
     *
     * 此方法的调用与调用的行为完全相同 join(0) 
     *
     * @throws InterruptedException–如果任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
     */
    public final void join()throws InterruptedException{
            join(0);
        }
    
    /**
     * 等待此线程死亡的时间最多为给定的毫秒数。
     *
     * millis 为0表示永远等待。 
     *
     * 当前实现使用了调用 this.wait 的循环,基于 this.isAlive 为条件。
     *
     * 当线程终止时,调用 this.notifyAll 方法。建议应用程序不要在线程实例上使用wait、notify或notifyAll。 
     *
     * @param   millis 毫秒–以毫秒为单位的等待时间 
     *
     * @throws IllegalArgumentException–如果millis的值为负 
     *
     *          InterruptedException–如果任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
     */
    public final synchronized void join(long millis)
    throws InterruptedException{
        long base=System.currentTimeMillis();
        long now=0;
    
        //millis的值不能为负
        if(millis< 0){
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        //millis 为0表示永远等待
        if(millis==0){
            while(isAlive()){
                wait(0);
            }
        }else{
            while(isAlive()){
                long delay=millis-now;
                if(delay<=0){
                    break;
                }
                // 线程等待指定时间
                wait(delay);
                now=System.currentTimeMillis()-base;
            }
        }
    }
    

    关于 wait 的源码解读请参考我的博客——结合JDK源码图文详解 wait 和 notify 的工作原理

    /**
     * 对调度程序的一个提示,表示当前线程愿意放弃当前对处理器的使用。
     *
     * 调度程序可以随意忽略此提示。 
     *
     * yield 是一种启发式的尝试,旨在改善线程之间的相对进程,否则会过度使用CPU。
     *
     * 它的使用应该与详细的分析和基准测试相结合,以确保它实际具有预期的效果。 
     *
     * 使用这种方法很少合适。
     *
     * 它可能对调试或测试有用,因为它可能有助于再现由于竞争条件而产生的bug。
     *
     * 在设计并发控制结构(如java.util.concurrent.locks包中的结构)时,它可能也很有用。
     */
    public static native void yield();
    

    实践

    package com.shockang.study.java.concurrent.thread.join;
    
    public class JoinDemo {
        public volatile static int i = 0;
    
        public static class AddThread extends Thread {
            @Override
            public void run() {
                for (i = 0; i < 10000000; i++) ;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            AddThread at = new AddThread();
            at.start();
            at.join();
            System.out.println(i);
        }
    }
    
    package com.shockang.study.java.concurrent.thread.join;
    
    /**
     * 中断状态可以检测,并在应用上作出相应
     * 如果应用不相应中断,则T1永远不会退出
     *
     * @author Shockang
     */
    public class YieldDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        if (Thread.currentThread().isInterrupted()) {
                            System.out.println("Interrupted!");
                            break;
                        }
                        Thread.yield();
                    }
                }
            };
            t1.start();
            Thread.sleep(2000);
            t1.interrupt();
        }
    }
    
    cs