12.2 多線程實現方式
線程的概念雖然比較復雜,但是在Java語言中實現線程卻比較簡單,只需要按照Java語言中對於線程的規定進行編程即可。
在實現線程編程時,首先需要讓一個類具備多線程的能力,繼承Thread類或實現Runnable接口的類具備多線程的能力,然後創建線程對象,調用對應的啟動線程方法開始執行即可實現多線程編程。
在一個程序中可以實現多個線程,多線程編程指在同一個程序中啟動了兩個或兩個以上的編程形式。當啟動的線程數量比較多時,對於系統資源的要求比較多,所以程序支持的最大線程數量和計算機的硬件配置相關。
在實際實現線程時,Java語言提供了三種實現方式:
1、繼承Thread類
2、實現Runnable接口
3、使用Timer和TimerTask組合
下面依次介紹每種實現方式的代碼編寫,以及各種實現之間的區別比較。
12.2.1 繼承Thread類
如果一個類繼承了Thread類,則該類就具備了多線程的能力,則該類則可以以多線程的方式進行執行。
但是由於Java語言中類的繼承是單重繼承,所以該方式受到比較大的限制。
下面以一個簡單的示例介紹該種多線程實現方式的使用以及啟動線程的方式。示例代碼如下所示:
/**
* 以繼承Thread的方式實現線程
*/
public class FirstThread extends Thread{
public static void main(String[] args) {
//初始化線程
FirstThread ft = new FirstThread();
//啟動線程
ft.start();
try{
for(int i = 0;i < 10;i++){
//延時1秒
Thread.sleep(1000);
System.out.println("main:" + i);
}
}catch(Exception e){}
}
public void run(){
try{
for(int i = 0;i < 10;i++){
//延時1秒
Thread.sleep(1000);
System.out.println("run:" + i);
}
}catch(Exception e){}
}
}
在該程序中,通過使FirstThread繼承Thread類,則FirstThread類具備了多線程的能力,按照Java語言線程編程的規定,線程的代碼必須書寫在run方法內部或者在run方法內部進行調用,在示例的代碼中的run方法實現的代碼作用是每隔1秒輸出一行文字。換句話說,run方法內部的代碼就是自定義線程代碼,或者說,自定義線程的代碼必須書寫在run方法的內部。
在執行FirstThread類時,和前面的執行流程一樣。當執行FirstThread類時,Java虛擬機將開啟一個系統線程來執行該類的main方法,main方法的內部代碼按照順序結構進行執行,首先執行線程對象的初始化,然後執行調用start方法。該行代碼的作用是啟動線程,在執行start方法時,不阻塞程序的執行,start方法的調用立刻返回,Java虛擬機以自己的方式啟動多線程,開始執行該線程對象的run方法。同時系統線程的執行流程繼續按照順序執行main方法後續的代碼,執行main方法內部的輸出。
這樣,在FirstThread執行時,就有了兩個同時執行的流程:main流程和自定義run方法流程,換句專業點的話來說,就是該程序在執行時有兩個線程:系統線程和自定義線程。這個同時執行可以從該程序的執行結果中獲得更加直接的證明。
該程序的執行結果為:
run:0
main:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
run:4
main:5
run:5
main:6
run:6
main:7
run:7
main:8
run:8
main:9
run:9
從執行結果可以看到兩個線程在同時執行,這將使我們進入多線程編程的時代,進入並發編程的領域,體會神奇的多線程編程的魔力。
由於兩個線程中的延遲時間——1秒,是比較長的,所以看到的結果是線程規律執行的,其實真正的線程執行順序是不能直接保證的,系統在執行多線程程序時只保證線程是交替執行的,至於那個線程先執行那個線程後執行,則無法獲得保證,需要書寫專門的代碼才可以保證執行的順序。
其實,上面的代碼可以簡化,簡化以後的代碼為:
/**
* 以繼承Thread的方式實現線程2
* 使用方法簡化代碼
*/
public class SecondThread extends Thread{
public static void main(String[] args) {
//初始化線程
SecondThread ft = new SecondThread();
//啟動線程
ft.start();
print("main:");
}
public void run(){
print("run:");
}
private static void print(String s){
try{
for(int i = 0;i < 10;i++){
//延時1秒
Thread.sleep(1000);
System.out.println(s + i);
}
}catch(Exception e){}
}
}
在該示例代碼中,將重復的代碼組織稱print方法,分別在main方法和run方法內部調用該方法。需要特別強調的是,在run方法內部調用的方法,也會以多線程多線程的方式被系統執行,這樣更加方便代碼的組織。
其實在實際實現時,還可以把線程以單獨類的形式出現,這樣實現的代碼如下所示:
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
//初始化線程
ThirdThread ft = new ThirdThread();
//啟動線程
ft.start();
try{
for(int i = 0;i < 10;i++){
//延時1秒
Thread.sleep(1000);
System.out.println("main:" + i);
}
}catch(Exception e){}
}
}
/**
* 以繼承Thread類的方式實現多線程3
* 以單獨類的實現組織代碼
*/
public class ThirdThread extends Thread {
public void run(){
try{
for(int i = 0;i < 10;i++){
//延時1秒
Thread.sleep(1000);
System.out.println("run:" + i);
}
}catch(Exception e){}
}
}
在該示例代碼中,ThirdThread類是一個單獨的線程類,在該類的run方法內部實現線程的邏輯,使用該種結構符合面向對象組織代碼的方式。需要啟動該線程時,和前面啟動的方式一致。
一個類具備了多線程的能力以後,可以在程序中需要的位置進行啟動,而不僅僅是在main方法內部啟動。
對於同一個線程類,也可以啟動多個相同的線程,例如以ThirdThread類為例,啟動兩次的代碼為:
ThirdThread t1 = new ThirdThread();
t1.start();
ThirdThread t2 = new ThirdThread();
t2.start();
而下面的代碼是錯誤的
ThirdThread t1 = new ThirdThread();
t1.start();
t1.start(); //同一個線程不能啟動兩次
當自定義線程中的run方法執行完成以後,則自定義線程將自然死亡。而對於系統線程來說,只有當main方法執行結束,而且啟動的其它線程都結束以後,才會結束。當系統線程執行結束以後,則程序的執行才真正結束。
總之,繼承Thread類可以使該類具備多線程的能力,需要啟動該線程時,只需要創建該類的對象,然後調用該對象中的start方法,則系統將自動以多線程的發那個是執行該對象中的run方法了。
雖然該種方式受到Java語法中類的單重繼承的限制,但是在實際的項目中還是獲得了比較廣泛的使用,是一種最基本的實現線程的方式。