前言

本文隶属于专栏《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();
    }
}
上一篇 下一篇