(See http://www.suneca.com/article.asp?id=54)
Thread API 包含了等待另一個線程完成的方法:join() 方法。當調用 Thread.join() 時,調用線程將阻塞,直到被join方法加入的目標線程完成爲止。 可以於解起來抽象一睦,現在我們來舉一個例子說明問題。
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public void run() {
//循環輸出1 到 100
for(int i=1;i<=10;i++){
System.out.println("子線程輸出:" + i);
}
}
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread();
//線程進入運行態
t1.start();
//主線程輸出
System.out.println("主線程輸出:http://www.suneca.com");
}
}
輸出結果爲:
從結果我們可以看到,對於我們運行MyThread這個程序,在主線程當中創建一個新的子線程,新的子線程啓動後,主線程輸出博客地址。因爲當前主線程處於運行態,而子線程是處於可運行態,所以輸出的結果爲先輸出網址,再輸出了線程輸出的數據。但也有可能會出現這種情況,子線程進入可運行態之後,馬上進入運行態,那此時的是輸出子線程的數據,再輸出主線程的數據,它們的執行完全是由線程調度器進行調度。我們再將程序做如下的修改:
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public void run() {
//循環輸出1 到 100
for(int i=1;i<=100;i++){
System.out.println("子線程輸出:" + i);
}
}
/**
* 應用程序入口.
*
* @param args
*/
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread();
//線程進入運行態
t1.start();
//主線程輸出
for(int i=0;i<100;i++){
System.out.println("主線程輸出:http://www.suneca.com");
}
}
}
此時的輸出結果爲:
主線程跟子線程在線程調度器的調度下,相互搶奪CPU資源,交叉運行着!
那麼,如果我希望程序啓用子線程,必須等待着子線程執行完畢之後,主線程才能繼續執行下去,那該怎麼辦?那此時,我們就必須使用到join這個方法啦!當使用Thread.join()時,調用線程將阻塞。因爲子線程是由主線程進行調用的,所以當子線程調用到join這個函數時,主線程將阻塞,必須等待子線程執行完畢之後,才能繼續執行。使用join的程序:
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public void run() {
//循環輸出1 到 100
for(int i=1;i<=10;i++){
System.out.println("子線程輸出:" + i);
}
}
/**
* 應用程序入口.
*
* @param args
*/
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread();
//線程進入運行態
t1.start();
try {
//線程的調用者,即主線程阻塞!
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//主線程輸出
for(int i=0;i<10;i++){
System.out.println("主線程輸出:http://www.suneca.com");
}
}
}
輸出結果爲:
從結果當中,我們可以看出,當子線程調用到join的時候,主線程將阻塞,子線程執行完畢之後,主線程才能繼續執行。對於join()方法,還有兩個重載方法,比如joing(long millis),那就是讓主程序阻塞多長時間後才能恢復到可運行狀態。
如果主程序調用了兩個子線程,那這兩個子線程是如何工作的呢?
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
public void run() {
//循環輸出1 到 100
for(int i=1;i<=100;i++){
System.out.println("子線程輸出:" + this.getName() + " - " + i);
}
}
/**
* 應用程序入口.
*
* @param args
*/
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
//線程進入運行態
t1.start();
t2.start();
try {
//線程的調用者,即主線程阻塞!
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//主線程輸出
for(int i=0;i<10;i++){
System.out.println("主線程輸出:http://www.suneca.com");
}
}
}
輸出結果:
從圖的輸出結果我們可以看出:對於在線程t1跟線程t2當中都調用了線程的join()方法,那麼,調用t1、t2的主線程將阻塞,等到t1、t2都執行完畢之後,再執行主線程。對於子線程t1跟t2,它們的一個先後順序,如何進行調度,完全是由線程調度器進行管理!
五、多線程
從前面的例子我們已經可以看出,對於有兩個以上的線程同時啓動,那這些線程的執行的先後順序我們是沒有辦法預知的,因爲對於它們的調用完全是由線程調度器進行調用!當子線程t1跟t2同時處於運行狀態,那誰先執行誰慢執行,由線程調度器決定。那我們程序能否進行控制呢?答案是可以的,我們可以通過設計優先級別來進行控制。每一個線程,默認的優先級別爲5,值越高,表示優先級別越高(最高爲10),值越低(最低爲1),表示優先級別越低!當線先級別相等的時候,那就只有排隊等待着線程調度器的調用。
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
public void run() {
//循環輸出1 到 100
for(int i=1;i<=10;i++){
System.out.println("子線程輸出:" + this.getName() + " - " + i);
}
}
/**
* 應用程序入口.
*
* @param args
*/
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
//線程進入運行態
t1.start();
t2.start();
//主線程輸出
System.out.println("主線程輸出:http://www.suneca.com");
}
}
t1比t2先啓動,進入可運行態,排隊等候線程的調度。執行結果:
如果設置了線程的優先級,如:
/**
* 使用繼承方式的線程實現.
*
* @author <a href="http://www.suneca.com">zizz</a>
*
* @Create-Time:2008 下午10:25:33
*/
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
public void run() {
//循環輸出1 到 100
for(int i=1;i<=10;i++){
System.out.println("子線程輸出:" + this.getName() + " - " + i);
}
}
/**
* 應用程序入口.
*
* @param args
*/
public static void main(String[] args){
//創建一個線程實例
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
//設置t1的的優先級別爲最低
t1.setPriority(Thread.MIN_PRIORITY);
//設置t2的優先級別爲最高
t2.setPriority(Thread.MAX_PRIORITY);
//線程進入運行態
t1.start();
t2.start();
//主線程輸出
System.out.println("主線程輸出:http://www.suneca.com");
}
}
執行結果:
從結果當中我們可以看到,最早的時候是由主線程執行,主線程佔用着CPU資源,接着,線程t1啓動,線程t1進入排隊,等候着線程調度器的調度;再接着,線程t2啓動,線程t2也進入排隊。
我們從結果當中更可以發現一點,對於高於5的線程優先級別,它更容易從當中正在執行的線程(主線程)當中搶奪CPU資源。
多線程的另外一種情況就是,多個線程共享同一個線程實例。
/**
* 使用實現Runnable接口的線程.
*
* @author <a href='http://www.suneca.com'>ZIZZ</a>
*
* @Create-Time:2008 下午10:48:31
*/
public class MyRunnable implements Runnable{
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
public static void main(String[] args){
//構造一個Runnable的實例
MyRunnable runnable = new MyRunnable();
//創建新的線程
Thread t1 = new Thread(runnable,"T1");
Thread t2 = new Thread(runnable,"T2");
//線程啓動.
t1.start();
t2.start();
}
}
執行結果片斷:
當多線程共享同一個線程實例的時候,我們需要考慮一下線程的同步問題。