0.Join()
執行緒的合併的含義就是 將幾個並行執行緒的執行緒合併為一個單執行緒執行,應用場景是 當一個執行緒必須等待另一個執行緒執行完畢才能執行時,Thread類提供了join方法來完成這個功能,注意,它不是靜態方法。
join有3個過載的方法:
void join():當前執行緒等該加入該執行緒後面,等待該執行緒終止。
void join(long millis):當前執行緒等待該執行緒終止的時間最長為 millis 毫秒。 如果在millis時間內,該執行緒沒有執行完,那麼當前執行緒進入就緒狀態,重新等待cpu排程。
void join(long millis,int nanos):等待該執行緒終止的時間最長為 millis 毫秒 + nanos納秒。如果在millis時間內,該執行緒沒有執行完,那麼當前執行緒進入就緒狀態,重新等待cpu排程。
參考:猿碼道:啃碎併發(二)
1.使用方法
新建一個Thread類,重寫run()方法:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("子執行緒執行完畢");
}
}
複製程式碼
新建測試類,測試Join()方法:
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("~~~~~~~~~~~~~~~");
}
}
}
複製程式碼
輸出結果如下:
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
子執行緒執行完畢
主執行緒執行完畢
~~~~~~~~~~~~~~~
複製程式碼
結果分析: 子執行緒每次都在主執行緒之前執行完畢,即子執行緒會在主執行緒之前執行。
程式碼中迴圈5次是為了排除執行緒執行的隨機性,若不能說明問題,可以調大迴圈次數進行測試。
2.原理分析
檢視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()方法中等待的主執行緒被喚醒,並獲得鎖,列印“主執行緒執行完畢”。
3.總結
Join()方法,使呼叫此方法的執行緒wait()(在例子中是main執行緒),直到呼叫此方法的執行緒物件(在例子中是MyThread物件)所在的執行緒(在例子中是子執行緒)執行完畢後被喚醒。
由於執行緒的啟動與銷燬其實是由作業系統進行操作,所以在描述的時候刻意略去,如果有疑惑的地方,可以檢視C++編寫的本地方法。
複製程式碼