在某些場景下,我們必須等待執行緒執行完成才能進行下一步工作。例如,某些程式在開始執行之前,需要先初始化一些資源。這時,我們可以啟動一個執行緒專門來做初始化任務,等到執行緒任務完成後,再去執行其他部分。
為此,Thread類別為我們提供了join()方法。當我們使用執行緒物件呼叫此方法時,正在掉呼叫的執行緒物件將被推遲到被呼叫物件執行完成後再開始執行。
在本節,範例程式示範等待初始化方法完成後,再去執行其他任務。
知其然
請依照下面所示步驟,完成範例程式。
1.建立一個名為DataSourcesLoader的類,並且實作Runnable介面。程式碼如下:
複製代碼代碼如下:
public class DataSourcesLoader implements Runnable {
2.實作run()方法,向控制台列印出一條訊息以說明開始執行,然後睡眠4秒鐘,再向控制台列印一條訊息來說明執行緒執行結束。程式碼如下:
複製代碼代碼如下:
@Override
public void run() {
System.out.printf("Beginning data sources loading: %s/n",
new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Data sources loading has finished: %s/n",
new Date());
}
3.建立一個名為NetworkConnectionsLoader的類,並且實作Runnable介面。 實作run()方法,此方法程式碼與DataSourcesLoader類別的run()方法一樣,只是這個睡眠6秒鐘。
4.實作範例的主類,並且實作main()方法。程式碼如下:
複製代碼代碼如下:
public class Main {
public static void main(String[] args) {
5.建立一個DataSourcesLoader對象,以及一個啟動它執行的Thread對象。程式碼如下:
複製代碼代碼如下:
DataSourcesLoader dsLoader = new DataSourcesLoader();
Thread thread1 = new Thread(dsLoader, "DataSourcesLoader");
6.建立一個NetworkConnectionsLoader對象,以及一個啟動它執行的Thread對象。程式碼如下:
複製代碼代碼如下:
NetworkConnectionsLoader ncLoader = new NetworkConnectionsLoader();
Thread thread2 = new Thread(ncLoader, "NetworkConnectionsLoader");
7.呼叫兩個Thread物件的start()方法。程式碼如下:
複製代碼代碼如下:
thread1.start();
thread2.start();
8.呼叫join()方法,來等待兩個執行緒完成其任務。這個方法會拋出InterruptedException異常,所以要捕捉該異常。程式碼如下:
複製代碼代碼如下:
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
9.向控制台列印一句話,表示程式執行結束。程式碼如下:
複製代碼代碼如下:
System.out.printf("Main: Configuration has been loaded: %s/n",
new Date());
10.運行程序,查看執行效果。
知其所以然
當運行這個範例程式時,我們可以看到兩個執行緒啟動了它們的執行。首先,是DataSourcesLoader完成了它的執行;然後,是NetworkConnectionsLoader完成了它的執行。這時,主執行緒繼續它的執行,然後向控制台列印終止訊息。
永無止境
Java提供了另外兩種重載的join()方法:
複製代碼代碼如下:
join(long milliseconds)
join(long milliseconds, long nanos)
第一種方式,不會直到被呼叫完成任務,而是等待參數指定的時間後就開始執行;例如,如果thread1調用該方法,thread1.join(1000),當thread1線程滿足如下其中之一的條件就會繼續執行:
1.thread2完成它的執行;
2.1000毫秒過後;
當這兩個條件中的其中之一為真時,join()方法就會返回,開始繼續執行原來的任務。
第二種方式的方法和第一種很類似,只是多了一個奈秒的時間參數。
拿來主義
本文是從《Java 7 Concurrency Cookbook》 (D瓜哥竊譯為《Java7並發範例集》 )翻譯而來,僅作為學習資料使用。沒有授權,不得用於任何商業行為。
小有所成
本節所用範例程式碼的完整版。
DataSourcesLoader類別的完整程式碼複製程式碼如下:
package com.diguage.books.concurrencycookbook.chapter1.recipe6;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Date: 2013-09-19
* Time: 09:15
*/
public class DataSourcesLoader implements Runnable {
@Override
public void run() {
System.out.printf("Beginning data sources loading: %s/n",
new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Data sources loading has finished: %s/n",
new Date());
}
}
NetworkConnectionsLoader類別的完整程式碼
複製代碼代碼如下:
package com.diguage.books.concurrencycookbook.chapter1.recipe6;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Date: 2013-09-19
* Time: 09:21
*/
public class NetworkConnectionsLoader implements Runnable {
@Override
public void run() {
System.out.printf("Beginning data sources loading: %s/n",
new Date());
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Data sources loading has finished: %s/n",
new Date());
}
}
Main類別的完整程式碼
複製代碼代碼如下:
package com.diguage.books.concurrencycookbook.chapter1.recipe6;
import java.util.Date;
/**
* Date: 2013-09-19
* Time: 09:25
*/
public class Main {
public static void main(String[] args) {
DataSourcesLoader dsLoader = new DataSourcesLoader();
Thread thread1 = new Thread(dsLoader, "DataSourcesLoader");
NetworkConnectionsLoader ncLoader = new NetworkConnectionsLoader();
Thread thread2 = new Thread(ncLoader, "NetworkConnectionsLoader");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Main: Configuration has been loaded: %s/n",
new Date());
}
}