Thread类中的join方法,用于等待某个线程执行结束。
简单示例
以下简单的代码,会让主线程等待子线程执行结束再执行。如果去掉t.join(),可能主线程就直接退出了,子线程都来不及执行。
package com.qcy.testJoin;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
t.join();
}
}
join源码分析
t.join()源码如下:
public final void join() throws InterruptedException {
join(0);
}
接着是调用join的单参数重载方法,传入等待时间0,表示一直等待下去
这一点也可以从重载方法的注释中看到
单参数的重载方法如下:以主线程调用t.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");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
(1)由于这里出现了synchronized,因此主线程需要拿到子线程对象t的锁。
(2)当millis为0的时候,循环判断isAlive(),即判断线程是否存活,如果存活,调用子线程对象的wait方法,传入0也是表示一直等待下去
public final native boolean isAlive();
(3)当主线程调用子线程对象的wait方法后,主线程释放掉锁,并进入等待状态。
(4)当子线程运行结束,不再是存活状态,那么主线程需要被唤醒,且需要再次获取到锁,才能继续运行join剩余的方法,运行结束后,主线程从join方法中返回,继续运行main剩余的方法。
JVM底层代码分析
问题来了,主线程什么时候被唤醒?唤醒代码又是在哪里的呢?
这要看jvm的底层代码,在thread.cpp代码中,地址thread.cpp地址
在线程退出的代码中,调用了ensure_join()方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
....
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
....
// Remove from list of active threads list, and notify VM thread if we are the last non-daemon thread
Threads::remove(this);
}
从ensure_join(this)上方的注释可以了解到,该方法是唤醒在该线程对象上的等待者。继续看他的源码
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
java_lang_Thread::set_thread(threadObj(), NULL)语句,由上面的注释,可以了解到该方法会清除本地线程实例,将会使得isAlive()返回false,接着在调用完lock.notify_all(thread)语句后,即通知在此线程对象上的等待者,我们例子中的主线程在重新获得CPU分配的时间片后,会直接从join方法中返回。
其他方式
至此,我们了解到了join方法的原理,不过join我们很少用到,在主线程等待子线程执行结束再执行的场景下,有更好更优雅的方法,可以参考我的另外一篇博客面试官:如何让主线程等待所有的子线程执行结束之后再执行?我懵了