【Java】Thread類中的join()方法原理
簡介
join()
是Thread類的一個方法。根據jdk文件的定義:
public final void join()throws InterruptedException: Waits for this thread to die.
join()
方法的作用,是等待這個執行緒結束;但顯然,這樣的定義並不清晰。個人認為”Java 7 Concurrency Cookbook”的定義較為清晰:
join() method suspends the execution of the calling thread until the object called finishes its execution.
也就是說,t.join()方法阻塞呼叫此方法的執行緒(calling thread),直到執行緒t完成,此執行緒再繼續;通常用於在main()主執行緒內,等待其它執行緒完成再結束main()主執行緒。我們來看看下面的例子。
例子
我們對比一下下面這兩個例子,看看使用join()
方法的作用是什麼?
- 不使用
join()
方法的情況:
public static void main(String[] args){
System.out.println("MainThread run start.");
//啟動一個子執行緒
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA run start.");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("threadA run finished." );
}
});
threadA.start();
System.out.println("MainThread join before");
System.out.println("MainThread run finished.");
}
執行結果如下:
MainThread run start.
threadA run start.
MainThread join before
MainThread run finished.
threadA run finished.
因為上述子執行緒執行時間相對較長,所以是在主執行緒執行完畢之後才結束。
- 使用了
join()
方法的情況:
public static void main(String[] args){
System.out.println("MainThread run start.");
//啟動一個子執行緒
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA run start.");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("threadA run finished.");
}
});
threadA.start();
System.out.println("MainThread join before");
try {
threadA.join(); //呼叫join()
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MainThread run finished.");
}
執行結果如下:
MainThread run start.
threadA run start.
MainThread join before
threadA run finished.
MainThread run finished.
對子執行緒threadA使用了join()
方法之後,我們發現主執行緒會等待子執行緒執行完成之後才往後執行。
join()
的原理和作用
java層次的狀態轉換圖
我們來深入原始碼瞭解一下join():
//Thread類中
public final void join() throws InterruptedException {
join(0);
}
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) { //這個分支是無限期等待直到b執行緒結束
while (isAlive()) {
wait(0);
}
} else { //這個分支是等待固定時間,如果b沒結束,那麼就不等待了。
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我們重點關注一下這兩句,無限期等待的情況::
while (isAlive()) {
wait(0); //wait操作,那必然有synchronized與之對應
}
注意這個wait()
方法是Object類中的方法,再來看sychronized的是誰:
public final synchronized void join(long millis) throws InterruptedException { ... }
成員方法加了synchronized說明是synchronized(this)
,this是誰啊?this就是threadA子執行緒物件本身。也就是說,主執行緒持有了threadA這個物件的鎖。
大家都知道,有了wait()
,必然有notify()
,什麼時候才會notify呢?在jvm原始碼裡:
// 位於/hotspot/src/share/vm/runtime/thread.cpp中
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);
// ...
}
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);
// 同志們看到了沒,別的不用看,就看這一句
// thread就是當前執行緒,是啥?就是剛才例子中說的threadA執行緒啊。
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
當子執行緒threadA執行完畢的時候,jvm會自動喚醒阻塞在threadA物件上的執行緒,在我們的例子中也就是主執行緒。至此,threadA執行緒物件被notifyall了,那麼主執行緒也就能繼續跑下去了。
可以看出,join()
方法實現是通過wait()
(小提示:Object 提供的方法)。 當main執行緒呼叫threadA.join時候,main執行緒會獲得執行緒物件threadA的鎖(wait 意味著拿到該物件的鎖),呼叫該物件的wait(等待時間),直到該物件喚醒main執行緒 (也就是子執行緒threadA執行完畢退出的時候)
總結
首先join()
是一個synchronized方法, 裡面呼叫了wait()
,這個過程的目的是讓持有這個同步鎖的執行緒進入等待,那麼誰持有了這個同步鎖呢?答案是主執行緒,因為主執行緒呼叫了threadA.join()
方法,相當於在threadA.join()
程式碼這塊寫了一個同步程式碼塊,誰去執行了這段程式碼呢,是主執行緒,所以主執行緒被wait()了。然後在子執行緒threadA執行完畢之後,JVM會呼叫lock.notify_all(thread);
喚醒持有threadA這個物件鎖的執行緒,也就是主執行緒,會繼續執行。