配圖:曲徑通幽java
講真,若是不是被面試官吊打,join()方法也還不會引發個人重視。由於,工做中確實沒有使用過它。面試
如今,對它來個刨根問底。c#
join()方法的做用多線程
在寫這篇文章以前,我對join的理解只停留在字面意思「把指定線程加入到當前線程」。併發
再來看官方怎麼解釋的:app
//Waits for this thread to die. public final void join() throws InterruptedException { join(0); }
「Waits for this thread to die.」,也就是等着join()方法所屬的 線程死亡(run方法執行完畢正常結束或線程異常死亡)。框架
下面舉個例子,證明這個說法。jvm
舉個栗子this
import static java.lang.System.out; public class JoinTest { public static void main(String args[]) throws InterruptedException{ String threadName = Thread.currentThread().getName(); out.println(threadName + " is Started"); Thread th1 = new FooThread(); th1.start(); th1.join(); out.println(threadName + " is Completed"); } } public class FooThread extends Thread{ public void run(){ try { String threadName = Thread.currentThread().getName(); out.println(threadName + " is Started"); Thread.sleep(2000); out.println(threadName + " is Completed"); } catch (InterruptedException ex) { ex.printStackTrace(); } } }
輸出結果:google
main is Started Thread-0 is Started Thread-0 is Completed main is Completed
例子中,首先主線程main thread開始執行,接着主線程建立並啓動了另一個線程「Thread-0」。由於Thread-0睡了2秒,因此線程Thread-0至少須要2秒才能執行完成。通常情景,主線程啓動線程Thread-0後,會繼續本身的工做,而不關心線程Thread-0的執行狀況。可是因爲join()方法的調用,主線程必須等待,直到Thread-0執行完成,主線程才能夠繼續執行後面的代碼。這個執行順序經過輸出結果也能夠看出。
也能夠說線程Thread-0加入了正在執行的主線程,這樣理解更貼切方法名join。
繼續看源代碼{join(0)}方法的實現。
實現原理
/* * Waits at most millis milliseconds for this thread to die. * A timeout of 0 means to wait forever. */ 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) {//join(0) while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
註釋的意思是,等待這個線程最多millis毫秒,millis毫秒後,無論這個線程有沒有死亡,主線程繼續執行。
超時時間爲0,意味着主線程要永遠等待。
進入{if(millis === 0)}條件模塊後,首先根據isAlive()判斷這個線程(例子中的Thread-0線程)是否還活着,若是活着,阻塞主線程(例子中的main線程)。把{wait(0)}放在while循環中,是爲了防止主線程阻塞期間被其餘線程喚醒。
也就說,此時主線程main會被一直阻塞,直到Thread-0線程執行結束。
But !
Thread-0執行結束後,即whie(isAlive)返回false時,join方法隨之也就結束了。並無看到喚醒主線程的代碼?
其實join方法的註釋中還有這樣一句話:
「As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.」
當一個線程結束的時候,會主動調用{this.notifyAll}喚醒全部等待該線程對象鎖(例子中的th1)的全部線程。而且,不建議在應用程序中調用該線程對象的wait,notify,notifyAll方法。
看完這句話,前一秒豁然開朗;後一秒,我仍是想問「notifyAll在哪調用的,仍是沒看到?」
帶着這個問題我google了一下,找到了答案。答案說這段代碼在jvm code中,並無貼出具體代碼。
本身找唄。
JVM源碼
我並無下載HotSpot的代碼,由於龐大且複雜,想找到目標代碼不容易。而是下載了超小型Java虛擬機JamVM。有多小?HotSpot源代碼一百多兆,JamVM只有656kb,你感覺下?(表情:苦笑、苦笑)
麻雀雖小五臟俱全,JamVM的目標是支持最新版的Java虛擬機規範。研究JVM原理,它是個不錯的入門選擇。
thread.c#threadStart(void *arg)
threadStart負責初始化並執行線程的run方法
void *threadStart(void *arg){ Thread *thread = (Thread *)arg; Object *jThread = thread->ee->thread; enableSuspend(thread); //初始化線程結構體,建立線程棧等 initThread(thread, INST_DATA(jThread, int, daemon_offset), &thread); /* Add thread to thread ID map hash table. */ addThreadToHash(thread); /* 執行線程的run方法 */ executeMethod(jThread, CLASS_CB(jThread->class)->method_table[run_mtbl_idx]); /* run方法執行完畢。分離線程並退出 */ detachThread(thread); TRACE("Thread 0x%x id: %d exited\n", thread, thread->id); return NULL; }
能夠看到,detachThread方法負責善後線程執行結束後的工做。
thread.c#detachThread(Thread *thread)
void detachThread(Thread *thread) { //省略... /* Remove thread from the ID map hash table */ deleteThreadFromHash(thread); /* 喚醒全部等待VMThread對象的線程 */ objectLock(vmthread); objectNotifyAll(vmthread); objectUnlock(vmthread); /* Disable suspend to protect lock operation */ disableSuspend(thread); /* 從Thread鏈表中刪除 */ if((thread->prev->next = thread->next)) thread->next->prev = thread->prev; /* 線程數減一 */ threads_count--; /* 回收線程ID */ freeThreadID(thread->id); /* 釋放系統資源*/ sysFree(ee->stack); sysFree(ee); //省略... }
在善後工做中,首先要作的就是喚醒全部在等待該線程對象的線程,而後是回收系統資源等。
再談join的應用
join方法能夠實現讓多線程按指定順序執行,這點在須要多線程相互協做工做的業務場景中很重要。
需求:計算1+2+3+...+100的結果。
爲了提升計算速度,咱們啓動兩個線程並行計算,線程leftThread計算1到50的和,線程rightThread計算51到100的和。
線程sumThread負責合併最後的計算結果,因此線程sunThread必須等待leftThread和rightThread執行結束後,才能計算最後的結果,這裏就須要把兩個計算線程join到sumThread線程中。sumThread的run方法以下:
public void run(){ int leftResult = leftThread.join(); int rightResult = rightThread.join(); sum = leftResult + right Result; }
But !
有個問題,join方法是不能返回線程的計算結果的。怎麼辦?
幸運的是,JDK中爲咱們提供了現成的解決方案。在jdk7中,concurrent包的做者Doug Lea給咱們帶來了一個高效的並行計算框架Fork/Join。Fork/Join模式極大的簡化了開發併發程序的繁瑣工做。But! 這個框架不是這篇文章的重點,之因此提到它是由於這是join方法的一個頗有力的應用案例。感興趣本身研究一下吧。