3.1 用法
下源码里对这个方法的描述。
// Thread.java
/**
* 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);
}
复制代码
源码里对这个方法的描述只有简单的一句话“等待这个线程的消亡”,也就说一个线程在调用另一个线程的join
方法后就要等待这个线程消亡后才能继续往下执行,相当于把并发的线程在这个时间点变成串行执行序列了。
在理解了这点后,再回过头来看看上面的题目,在thread1
和thread2
的死锁等待方面的分析都是正确的,关键点在于主线程在这之后是否还可以继续往下执行。由于在主线程中调用了thread1.join()
和thread2.join()
,就表明主线程必须等待这两个线程执行完才能继续执行,但thread1
和thread2
已经处于死锁状态,是不可能消亡的,这也就导致主线程无法继续下去了,所以最后的输出结果应该是:
thread1 start
thread2 start
复制代码
我自己也在回来之后运行过这段代码,结果和分析的一致,也算弄明白了Thread.join()
是咋回事了。
3.2 实现原理
在弄明白Thread.join()
的用法和含义是不是就圆满结束了?当然不是,我们尽可能地了解其内部的实现原理。
简单来说就是要知道两个问题:
- 如何让当前线程在调用
Thread.join()
之后停止执行,直到另一个线程消亡的? - 在另一个线程消亡后,当前线程是如何继续开始执行的?
3.2.1 如何停止
一切来源于代码,我们自然要到代码去寻找答案,还是再来看下Thread.join()
的声明和定义:
// Thread.java
/**
* Waits for this thread to die.
*/
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.
*
*/
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()) {
// 等待时间没有直接使用参数指定的 millis,原因是为了保持退出循环的可能。
long delay = millis - now;
if (delay <= 0) {
break;
}
// 当线程还处于存活状态时,就等待一段时间。
wait(delay);
// 更新 now 时间信息,是为了等待时间结束后,再次进到这个循环时能够由于 delay <= 0 而直接退出循环。
now = System.currentTimeMillis() - base;
}
}
}
复制代码
这个函数的代码量并不大,逻辑也比较容易理解,就是在线程A中调用线程B的join()
方法后,这个线程A就会处于对线程B的wait
状态,根据传入的参数不同可以处于一直等待也可以只等待一段时间。
3.2.2 如何恢复
既然线程A在调用线程B的join
方法后就会处于wait
状态,那线程A又是在何时恢复执行的呢?这里只介绍不带参数的join
方法,即一直等待的情况。从join
方法的介绍中可知,要等到线程B的消亡,线程A才能恢复,这是如何实现的呢?
// Thread.java
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
// 调用销毁回调
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
复制代码
在线程真正退出之前,系统会调用exit
方法来进行一些回收操作,从代码可以看到除了group.threadTerminated()
之外都是一些置空操作,很可能起到恢复作用的逻辑就藏在group.threadTerminated()
里面,这里的group
是ThreadGroup
的实例,是线程在初始化的时候创建的,可以简单理解为这个线程属于这类线程组的。
直接来看ThreadGroup.threadTerminated()
的代码:
/**
* Notifies the group that the thread {@code t} has terminated.
*
* <p> Destroy the group if all of the following conditions are
* true: this is a daemon thread group; there are no more alive
* or unstarted threads in the group; there are no subgroups in
* this thread group.
*
* @param t
* the Thread that has terminated
*/
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
// 唤醒所有的等待线程。
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
复制代码
很明显,在线程被销毁的时候会调用notifyAll()
来唤醒所有等待线程,所以线程A才能在线程B消亡的时候恢复运行。