前言
Thread#join()
内部调用了 同步方法 Thread#join(long millis)
,该方法 由 synchronized
修饰,该方法内部 又调用了 Object#wait(0)
注:Object#wait(0) 和 Object#wait() 一样,都是让调用此方法的线程进入永久阻塞,唯一的区别就是,Object#wait(0) 让线程 变为
TIMED_WAITING
状态,而,Object#wait() 则是让线程变为WAITING
状态。
Thread#join(long millis) 的 Javadoc
原文:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*/
大致意思是:最多等待当前线程 millis 毫秒,除非当前线程终止了。如果参数 millis=0 毫秒,那么,就意味着当前线程会进入永久阻塞。
示例:
public class MainThread {
public static void main(String[] args) {
System.out.println("mainThread " + Thread.currentThread().getState().name());
Thread childThread = new Thread(() -> {
try {
System.out.println("childThread " + Thread.currentThread().getState().name());
// childThread 休眠 3s
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "childThread");
// 启动子线程
childThread.start();
try {
// childThread 加入 mainThread
// 其实就是 mainThread 会获得 childThread 实例作为锁,因为 childThread.join(0) 是 synchronized 修饰的
// mainThread 调用了 childThread.wait(0) 进入永久阻塞
// 除非 childThread 结束(TERMINATED),释放所有资源,否则 mainThread 会一直 处于 TIMED_WAITING 状态
// 线程结束后,childThread 会调用 notifyAll, 唤醒所有的线程
childThread.join();
System.out.println("childThread " + childThread.getState().name());
System.out.println("mainThread " + Thread.currentThread().getState().name());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面代码展示了两个线程:mainThread(主线程),childThread(子线程)。
通过 Thread childThread = new Thread(..)
; 新建childThread线程(此时 childThread 线程处于NEW状态);
然后调用childThread.start()
(childThread线程状态转换为RUNNABLE);
再调用childThread.join()
,此时,mainThread 线程会阻塞,一直等待childThread 线程运行完再继续运行。
所以,
Thread#join
方法的大致作用为:线程B 使用 线程A 的 join方法,这个方法会阻塞线程B,直到线程A结束执行,线程B才会继续执行。
为此提出一个疑问?
- 线程B 使用 childThread.join()时,到底发生了啥?
Thread#join() 源码分析
Thread#join
一共有三个重载版本,分别是无参、一个参数、两个参数:
其中,
- 三个方法都被
final
修饰,无法被子类重写; - 有参的 join方法 都有
synchronized
关键字修饰,锁就是当前线程实例;
而执行 join方法 的另一个线程就会获得锁; - 无参版本 和 两个参数版本,最终都调用了仅有一个参数的版本;
- join() 和 join(0) 效果相同的,都表示永久阻塞;join(非0)表示等待一段时间;
从源码可以看到, join(0) 调用了
Object.wait(0)
,其中Object.wait(0)
会永久阻塞,直到被 notify/notifyAll 唤醒为止。
- join() 和
sleep()
一样,可以被中断(被中断时,会抛出InterrupptedException
异常);不同的是,join() 内部调用了 wait(),会释放锁,而 sleep() 会一直保持锁。
public final void join() throws InterruptedException;
public final synchronized void join(long millis)throws InterruptedException;
public final synchronized void join(long millis, int nanos)throws InterruptedException;
Thread.join()
// 同步方法,锁是线程的实例对象
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");
}
// 如果是 join(0),其实无参的 join方法就调用了 join(0)
if (millis == 0) {
// 循环,唤醒后再次判断,防止 虚假唤醒
while (isAlive()) {
// 调用 Object#wait(0), 永久阻塞
// 除非,锁调用 notifyAll 或者 notify
// 注:子线程(锁)结束后,就会调用 notifyAll
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
// 如果阻塞的时间 >= millis,直接结束
if (delay <= 0) {
break;
}
// 阻塞 millis 时间
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
分析
结合本文开头的实例 和 Thread#join() 源码,可以看到:
调用链:MainThread.main() -> childThread.join() -> childThread.join(0) -> childThread.wait(0)。
-
MainThread 线程会获得 childThread 线程实例作为锁,其他线程可以进入
childThread.join()
,但不可以进入childThread.join(0)
, 因为childThread.join(0)是同步方法; -
如果 childThread 线程是 Active,则调用 childThread.wait(0),为了防止childThread 被虚假唤醒, 需要将 wait(0) 放入 while(isAlive()) 循环中;
-
一旦 childThread 线程不为 Active (状态为 TERMINATED), 此时,childThread 线程就已经结束了,结束的线程最后都会调用 notifyAll 方法释放所有等待此线程锁的其他线程,MainThread 就是其中之一,所以,MainThread 被唤醒了,继续执行。
结论
- 子线程结束后,子线程的
this.notifyAll()
会被调用,join()返回,父线程只要获取到锁和CPU,就可以继续运行下去了。 - 调用 另一个线程的join()方法 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/69724.html