多執行緒 join 方法
多執行緒 join 方法
1.前言
本節對 join 方法進行深入的剖析,主要內容點如下:
- 瞭解 join 方法的作用,初步的理解 join 方法的使用帶來的效果是學習本節內容的基礎;
- 瞭解 join 方法異常處理,我們在使用 join 方法是,需要對 join 方法進行有效的異常處理;
- 通過匿名內部類建立Thread 是我們本節程式碼示例所使用的的方式,對這種方式的掌握在後續工作中非常重要;
- 掌握 join 方法如何使用,這是本節的重點內容,也是本節的核心內容;
- 掌握帶引數的 join 方法使用,在後續開發過程中,如果對介面的最大返回時間有要求的話,某些情況下會用到帶引數的 join
2. join 方法的作用
方法定義: 多執行緒環境下,如果需要確保某一執行緒執行完畢後才可繼續執行後續的程式碼,就可以通過使用 join 方法完成這一需求設計。
在專案實踐中經常會遇到一個場景,就是需要等待某幾件事情完成後主執行緒才能繼續往下執行, 比如多個執行緒載入資源, 需要等待多個執行緒全部載入完畢再彙總處理。
Thread 類中有一個 join 方法就可以做這個事情,join 方法是 Thread 類直接提供的。join 是無參且返回值為 void 的方法。
如上圖所示,假如有 3 個執行緒執行邏輯,執行緒 1 需要執行5秒鐘,執行緒 2 需要執行10 秒鐘,執行緒 3 需要執行 8 秒鐘。 如果我們的開發需求是:必須等 3 條執行緒都完成執行之後再進行後續的程式碼處理,這時候我們就需要使用到 join 方法。
第 5 秒鐘: 執行緒 1 執行完畢;執行緒 2 執行了一半; 執行緒 3 還差 3 秒執行完畢;
第 8 秒鐘: 執行緒 1 等待了 3 秒; 執行緒 3 執行完畢; 執行緒 2 還差 2 秒執行完畢;
第10 秒鐘: 執行緒 1 等待了 5 秒; 執行緒 3 等待了 2 秒;執行緒 2 執行完畢;
從執行緒 2 執行結束的那一刻:三條執行緒同時進行後續程式碼的執行。
這就是 join 方法的作用與解釋。
3. join 方法異常處理
join 方法是 Thread 類中的方法,為了瞭解該方法的異常處理,我們先來簡要的看下 join 方法的 JDK 原始碼:
public final void join() throws InterruptedException {
join(0);
}
從原始碼中我們可以看到, join 方法丟擲了異常:
throws InterruptedException
所以,我們在使用 join 方法的時候,需要對 join 方法的呼叫進行 try catch 處理或者從方法級別進行異常的丟擲。
try-catch 處理示例:
public class DemoTest implements Runnable{
@Override
public void run() {
System.out.println("執行緒:"+Thread.currentThread()+" 正在執行...");
}
public static void main(String[] args) {
Thread t1 = new Thread(new DemoTest());
t1. start();
try {
t1.join();
} catch (InterruptedException e) {
// 異常捕捉處理
}
}
}
throws 異常處理示例:
public class DemoTest implements Runnable throws InterruptedException {
@Override
public void run() {...}
public static void main(String[] args) {
Thread t1 = new Thread(new DemoTest());
t1. start();
t1.join();
}
}
4. join 方法如何使用
為了更好的瞭解 join 方法的使用,我們首先來設計一個使用的場景。
場景設計:
執行緒 1 : 執行時間 5 秒鐘;
執行緒 2 : 執行時間 10 秒鐘;
執行緒 3: 執行 8 秒鐘。
需求: 我們需要等 3 個執行緒都執行完畢後,再進行後續程式碼的執行。3 個執行緒執行完畢後,請列印執行時間。
期望結果: 10 秒執行時間。
看到這個是不是似曾相識呢? 這就是我們本節第 2 知識點所舉出的示例,現在我們來進行程式碼實現和驗證,體會 join 方法的使用。
例項:
public class DemoTest{
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() { //執行緒 1
@Override
public void run() {
try {
Thread.sleep (5000 ); //執行緒 1 休眠 5 秒鐘
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println ("執行緒 1 休眠 5 秒鐘,執行完畢。");
}
});
Thread threadTwo = new Thread(new Runnable() { //執行緒 2
...
Thread.sleep (10000 ); //執行緒 2 修眠 10 秒鐘
...
System.out.println ("執行緒 2 修眠 10 秒鐘,執行完畢。");
}
});
Thread threadThree = new Thread(new Runnable() {//執行緒 3
...
Thread.sleep (8000 ); //執行緒 3 修眠 8 秒鐘
...
System.out.println ("執行緒 3 修眠 8 秒鐘,執行完畢。");
}
});
Long startTime = System.currentTimeMillis();
threadOne. start();threadTwo. start();threadThree. start();
System.out.println("等待三個執行緒全部執行完畢再繼續向下執行,我要使用 join 方法了。");
threadOne.join(); //執行緒 1 呼叫 join 方法
threadTwo.join(); //執行緒 2 呼叫 join 方法
threadThree.join(); //執行緒 3 呼叫 join 方法
Long endTime = System.currentTimeMillis();
System.out.println("三個執行緒都執行完畢了,共用時: "+ (endTime - startTime) + "毫秒");
}
}
執行結果驗證:
等待三個執行緒全部執行完畢再繼續向下執行,我要使用 join 方法了。
執行緒 1 休眠 5 秒鐘,執行完畢。
執行緒 3 修眠 8 秒鐘,執行完畢。
執行緒 2 修眠 10 秒鐘,執行完畢。
三個執行緒都執行完畢了,共用時: 10002毫秒
從執行的結果來看,與我們對 join 方法的理解和分析完全相符。
5. 帶引數的 join 方法使用
除了無參的 join 方法以外, Thread 類還提供了有參 join 方法如下:
public final synchronized void join(long millis)
throws InterruptedException
該方法的引數 long millis 代表的是毫秒時間。
方法作用描述:等待 millis 毫秒終止執行緒,假如這段時間內該執行緒還沒執行完,也不會再繼續等待。
結合上一個知識點的程式碼,我們都是呼叫的無參 join 方法,現在對上一個知識點程式碼進行如下調整:
threadOne.join(); //執行緒 1 呼叫 join 方法
threadTwo.join(3000); //執行緒 2 呼叫 join 方法
threadThree.join(); //執行緒 3 呼叫 join 方法
從程式碼中我們看到,執行緒 2 使用 join 方法 3000 毫秒的等待時間,如果 3000 毫毛後,執行緒 2 還未執行完畢,那麼主執行緒則放棄等待執行緒 2,只關心執行緒 1 和執行緒 3。
我們來看下執行結果:
等待三個執行緒全部執行完畢再繼續向下執行,我要使用 join 方法了。
執行緒 1 休眠 5 秒鐘,執行完畢。
執行緒 3 修眠 8 秒鐘,執行完畢。
三個執行緒都執行完畢了,共用時: 8000毫秒
執行緒 2 修眠 10 秒鐘,執行完畢。
從執行結果來看, 總用時 8000 毫秒,因為執行緒 2 被放棄等待了,所以只考慮執行緒 1 和執行緒 3 的執行時間即可。
6. 小結
在實際的開發場景中,經常會設計到對 join 方法的使用,無參方法使用更加常見,瞭解 join 方法的使用非常重要。
本節重中之重,就是掌握 join 方法的使用,join 方法在後續的開發工作中非常關鍵,很多情況下都會有所使用。