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
方法详情可以参考 我的另一篇博文,如果有什么错误欢迎斧正,或有不明白的地方可以留联系方式交流交流。