線程的合併的含義就是 將幾個並行線程的線程合併爲一個單線程執行,應用場景是 當一個線程必須等待另外一個線程執行完畢才能執行時,Thread類提供了join方法來完成這個功能,注意,它不是靜態方法。bash
join有3個重載的方法:併發
void join():當前線程等該加入該線程後面,等待該線程終止。
void join(long millis):當前線程等待該線程終止的時間最長爲 millis 毫秒。 若是在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,從新等待cpu調度。
void join(long millis,int nanos):等待該線程終止的時間最長爲 millis 毫秒 + nanos納秒。若是在millis時間內,該線程沒有執行完,那麼當前線程進入就緒狀態,從新等待cpu調度。
ide
參考:猿碼道:啃碎併發(二)post
新建一個Thread類,重寫run()方法:測試
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("子線程執行完畢");
}
}
複製代碼
新建測試類,測試Join()方法:ui
public class TestThread {
public static void main(String[] args) {
//循環五次
for (int i = 0; i < 5; i++) {
MyThread thread = new MyThread();
//啓動線程
thread.start();
try {
//調用join()方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主線程執行完畢");
System.out.println("~~~~~~~~~~~~~~~");
}
}
}
複製代碼
輸出結果以下:this
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
子線程執行完畢
主線程執行完畢
~~~~~~~~~~~~~~~
複製代碼
結果分析: 子線程每次都在主線程以前執行完畢,即子線程會在主線程以前執行。spa
代碼中循環5次是爲了排除線程執行的隨機性,若不能說明問題,能夠調大循環次數進行測試。
操作系統
查看Thread類源碼:線程
public final void join() throws InterruptedException {
//當調用join()時,實際是調用join(long)方法
join(0);
}
複製代碼
查看Join(long)方法源碼:
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) { //因爲上一步傳入參數爲0,所以調用當前判斷
while (isAlive()) { //判斷子線程是否存活
wait(0); //調用wait(0)方法
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
複製代碼
查看isAlive()方法源碼:
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
* 測試線程是否還活着。若是線程存活的話它就是已經開始,尚未死亡的狀態。
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/****
public final native boolean isAlive();
複製代碼
說明: 該方法爲本地方法,判斷線程對象是否存活,若線程對象調用了start()方法,在沒有死亡的狀況下此判斷爲true。在上述例子中,因爲調用了子線程的start()方法,而且沒有結束操做,所以判斷true。
查看wait()方法源碼:
public final native void wait(long timeout) throws InterruptedException;
複製代碼
說明: 該方法爲本地方法,調用此方法的當前線程須要釋放鎖,並等待喚醒。在上述例子中,主線程調用子線程對象的join()方法,所以主線程在此位置須要釋放鎖,並進行等待。
wait()與wait(0)的區別:
查看wait()方法源碼,wait()方法只調用了wait(0),以下:
public final void wait() throws InterruptedException {
wait(0);
}
所以,wait()與wait(0)相同。
複製代碼
咱們來擼一擼上述步驟: 主線程wait()等待,子線程調用了run()執行,打印「子線程執行完畢」。此時,主線程還沒被喚醒,尚未執行下面的操做。那麼,問題來了,誰?在何時?喚醒了主線程呢?
查看Thread類中存在exit()方法,源碼以下:
/**
* 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) { //線程組在Thread初始化時建立,存有建立的子線程
group.threadTerminated(this); //調用threadTerminated()方法
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;
}
複製代碼
經過debug,exit()在線程執行完run()方法以後會被調用,此時線程組中存在當前子線程,所以會調用線程組的threadTerminated()方法。 查看ThreadGroup.threadTerminated()方法源碼:
/** Notifies the group that the thread {@code t} has terminated.
* 通知線程組,t線程已經終止。
*
void threadTerminated(Thread t) {
synchronized (this) {
remove(t); //從線程組中刪除此線程
if (nthreads == 0) { //當線程組中線程數爲0時
notifyAll(); //喚醒全部待定中的線程
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
複製代碼
經過此方法,將子線程從線程組中刪除,並喚醒其餘等待的線程。在上述例子中,此時子線程被銷燬,並釋放佔用的資源,並喚醒等待中的線程。而在join()方法中等待的主線程被喚醒,並得到鎖,打印「主線程執行完畢」。
Join()方法,使調用此方法的線程wait()(在例子中是main線程),直到調用此方法的線程對象(在例子中是MyThread對象)所在的線程(在例子中是子線程)執行完畢後被喚醒。
因爲線程的啓動與銷燬實際上是由操做系統進行操做,因此在描述的時候刻意略去,若是有疑惑的地方,能夠查看C++編寫的本地方法。
複製代碼