Java 线程 join 方法详解 -- ISS(Ideas Should Spread)

本文是笔者 Java 学习笔记之一,旨在总结个人学习 Java 过程中的心得体会,现将该笔记发表,希望能够帮助在这方面有疑惑的同行解决一点疑惑,我的目的也就达到了。欢迎分享和转载,转载请注明出处,谢谢合作。由于笔者水平有限,文中难免有所错误,希望读者朋友不吝赐教,发现错漏我也会更新修改,欢迎斧正。(可在文末评论区说明或索要联系方式进一步沟通。)

首先可以由一个面试题来引出 join 方法的使用,题目如下:

现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对 join 方法是否熟悉。这个多线程问题比较简单,可以用 join 方法实现。如:

public class ThreadJoin {

    public static void main (String[] args) throws InterruptedException {
        final Thread1 thread1 = new Thread1 ();
        final Thread2 thread2 = new Thread2 ();
        final Thread3 thread3 = new Thread3 ();
        thread1.start ();
        thread1.join ();
        thread2.start ();
        thread2.join ();
        thread3.start ();
        thread3.join ();
    }
}

class Thread1 extends Thread {
    @Override
    public void run () {
        for (int i = 0; i < 10; i++) {
            System.out.print (i + " ");
        }
    }
}


class Thread2 extends Thread {
    @Override
    public void run () {
        for (int i = 10; i < 20; i++) {
            System.out.print (i + " ");
        }
    }
}


class Thread3 extends Thread {
    @Override
    public void run () {
        for (int i = 20; i < 30; i++) {
            System.out.print (i + " ");
        }
    }
}

程序让第一个线程打印 0 到 9, 第一个线程打印 10 到 19,第三个线程打印 20 到 29,以上代码总是能够连续输出 0 到 29,如果将 join 方法的调用去掉则根据每次运行结果都可能不一致。

那么为什么 join 方法能够保证线程的顺序运行呢,先来看看 JDK 源码:

/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
    join(0);
}

/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param  millis
*         the time to wait in milliseconds
*
* @throws  IllegalArgumentException
*          if the value of {@code millis} is negative
*
* @throws  InterruptedException
*          if any thread has interrupted the current thread. The
*          <i>interrupted status</i> of the current thread is
*          cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

从注释中可以看到:

Waits for this thread to die.

也就是说调用这个线程(A)的 join 方法的那个线程(B)会等到这个线程(A) die,那么线程 B 进入等待状态直到 线程 A 死亡也就保证了 B 必须在 A 执行完毕才接着执行。

从源码角度可以看到 join 方法的功能,从使用角度来讲也许已经足够,如果要探究原理的话,源代码中也能提供一些线索,观察 join 实现源码可以发现其中最重要的就是使用 wait 方法了,如果对 wait 方法有所了解的话 join 的实现原理也不难理解了,原理如下:

  • 首先发现 join 方法使用了 synchronized 关键字修饰,也就是说在对线程(假设为 thread1) 调用该方法时候(即 thread1.join()),调用该方法的调用线程(如 main 主线程)会在拥有 thread1 的对象锁之后进入方法体执行;
  • 进入方法体后,一个 while(isAlive()) 循环会持续判断被调用的那个线程(thread1)是否还存活,关于 isAlive 的方法如下:

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive();
    

    可以看出两种情况下该循环会退出:

    • 线程 thread1 还没有开始,即还没有调用 thread1.start()
    • 线程 thread 已经死亡,即线程 die
  • 如果循环条件不满足,将会调用 wait(delay) 方法,注意此时相当于 this.wait(delay),而 this 代表被调用的线程对象(即 thread1),也就是调用线程(main 主线程)被加入 thread1 线程的对象等待集合,此时调用线程阻塞,thread1 会被调度(假设没有其它线程),循环一直重复直到 thread1 运行完毕循环条件不满足,调用线程(main 主线程)退出 join 方法,从调用 join 方法的下一行代码接着运行。实现线程的顺序执行。

关于 wait 方法

关于 wait 方法详情可以参考 我的另一篇博文,如果有什么错误欢迎斧正,或有不明白的地方可以留联系方式交流交流。

原文地址:https://www.cnblogs.com/keZhenxu94/p/5288477.html