淺談多線程
在Java語言中,同一個程序內部的併發處理由線程這個概念來實現。
1. 程序與進程
程序:一段靜態的代碼
進程:程序的一次動態執行過程,它對應從代碼加載、執行到執行完畢的一個完整過程。
進程也稱任務,支持多個進程同時執行的操作系統就被稱爲多進程操作系統或多任務操作系統
2. 進程與線程。
在一個程序內部也可以實現多個任務併發執行,其中每個任務稱爲線程。線程是比進程更小的執行單位,它是在一個進程中獨立的控制流,即程序內部的控制流。
特點:
線程不能獨立運行,必須依賴於進程,在進程中運行。
每個程序至少有一個線程稱爲主線程。
單線程:只有一條線程的進程稱爲單線程
多線程:有不止一個線程的進程稱爲多線程
3. 開啓多線程的優點和缺點
提高界面程序響應速度。
充分利用系統資源,提高效率。
當程序中的線程數量比較多時,系統將花費大量的時間進行線程的切換,這反而會降低程序的執行效率。但是,相對於優勢來說,劣勢還是很有限的,所以現在的項目開發中,多線程編程技術得到了廣泛的應用。
在實現線程編程時,首先需要讓一個類具備多線程的能力,繼承Thread類或實現Runnable接口的類具備多線程的能力,然後創建線程對象,調用對應的啓動線程方法即可實現線程編程。
在一個程序中可以實現多個線程,多線程編程指在同一個程序中啓動了兩個或兩個以上的線程。
4. 多線程的實現途徑
Java語言提供了三種實現方式:
ü 繼承Thread類
ü 實現Runnable接口
ü 使用Timer和TimerTask組合
但常用的是前兩種
a) 繼承Thread線程類實現多線程
java.lang包中提供了一個專門的線程類(Thread),在該類中封裝了許多對線程進行調度和處理的方法。如果一個類繼承了Thread類,則該類就具備了多線程的能力,可以多線程的方式執行。
如:
//繼承Thread類
class MyThread extends Thread{
//重寫run()方法
public void run(){
//線程體
}
Public static void main(String [] arg s) {
MyThread tt1 = new MyThread (); //創建線程對象
//啓動線程
tt1.start();//調用線程對象start()啓動線程
try{
for(int i = 0;i < 5;i++){
//延時1秒
Thread.sleep(1000);
System.out.println("Main:" + i);
}
}catch(Exception e){}
}
}
注意:
線程的特性:隨機性,系統在執行多線程程序時只保證線程是交替執行的,至於哪個線程先執行哪個線程後執行,則無法獲得保證,需要書寫專門的代碼纔可以保證執行的順序。
對於同一個線程類,也可以啓動多個線程。
Thread2 t2 = new Thread2(); t2.start();
Thread2 t3 = new Thread2(); t3.start();
同一個線程不能啓動兩次,例如
Thread2 t2 = new Thread2();
t2.start(); t2.start(); //錯誤
當自定義線程中的run方法執行完成以後,則自定義線程自然死亡。而對於系統線程來說,只有當main方法執行結束,而且啓動的其它線程都結束以後,纔會結束。當系統線程執行結束以後,程序的執行才真正結束。
b) 實現Runable接口
多線程對象實現java.lang.Runnable接口並且在該類中重寫Runnable接口的run方法。
好處:實現Runable接口的方法避免了單繼承的侷限性。
接口編程步驟:
1.實現java.lang.Runnable接口;
2.重寫Runnable接口的run方法;
3.創建Runnable接口的子類對象;
4.創建Thread類的對象,並將Runnable接口的子類對象作爲參數傳遞給Thread類的構造方法,最後調用Thread對象的start方法即可啓動線程。
Runnable1 r1 = new Runnable1();
Thread t1 = new Thread(r1);
t1.start();
5. 線程的生命週期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程,這就是所謂的生命週期。一個線程在它的生命週期內有5種狀態:
新建、就緒、運行、死亡、堵塞
新建
當創建Thread類的一個實例(對象)時,此線程進入新建狀態(未被啓動)。
例如:Thread t1=new Thread();
就緒
線程已經被啓動,正在等待被分配給CPU時間片,也就是說此時線程正在就緒隊列中排隊等候得到CPU資源。例如:t1.start();
運行
線程獲得CPU資源正在執行任務(run()方法),此時除非此線程自動放棄CPU資源或者有優先級更高的線程進入,線程將一直運行到結束。
死亡
當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不可能再進入就緒狀態等待執行。
ü 自然終止:正常運行run()方法後終止
ü 異常終止:調用stop()方法讓一個線程終止運行
堵塞
由於某種原因導致正在運行的線程讓出CPU並暫停自己的執行,即進入堵塞狀態。
ü 正在睡眠:用sleep(long t) 方法可使線程進入睡眠方式。一個睡眠着的線程在指定的時間過去可進入就緒狀態。
ü 正在等待:調用wait()方法。(調用notify()方法回到就緒狀態)
ü 被另一個線程所阻塞:調用suspend()方法。(調用resume()方法恢復)
6. 線程的優先級
線程從就緒狀態進入運行狀態的過程叫做線程調度。
負責調度工作的機構叫做調度管理器。
優先級:線程的優先級的取值範圍是1~10。
MAX_PRIORITY = 10
NORM_PRIORITY = 5
MIN_PRIORITY = 1
得到或修改線程的優先級
public final int getPriority();
public final void setPriority(int newPriority);
void run() //創建該類的子類時必須實現的方法
void start() //開啓線程的方法
static void sleep(long t) //釋放CPU的執行權,不釋放鎖
static void sleep(long millis,int nanos)
final void wait()//釋放CPU的執行權,釋放鎖
final void notify()
static void yied()//可以對當前線程進行臨時暫停(讓線程將資源釋放出來)
public final void stop()//結束線程,但由於安全的原因過時
注意:結束線程原理---就是讓run方法結束。而run方法中通常會定義循環結構,所以只要控制住循環即可。
方法----可以boolean標記的形式完成,只要在某一情況下將標記改變,讓循環停止即可讓線程結束。但是,特殊情況,線程在運行過程中,處於了凍結狀態,是不可能讀取標記的。
那麼這時,可以通過正常方式恢復到可運行狀態,也可以強制讓線程恢復到可運行狀態,通過Thread類中的,interrupt():清除線程的凍結狀態,但這種強制清除會發生InterruptedException。所以在使用 wait,sleep,join方法的時候都需要進行異常處理。
interrupt()方法:中斷線程
但實際上該方法不會中斷正在執行的線程,只是將線程的標誌位設置成true(可以用isInterrupted()方法測試該線程的中斷標記,並不清除中斷標記,static的方法interrupted()測試當前執行的線程是否被中斷,並且在肯定的情況下,清除當前線程對象的中斷標記並返回true);
如果線程在調用sleep(),join(),wait()方法時線程被中斷,則這些方法會拋出InterruptedException,在catch塊中捕獲到這個異常時,線程的中斷標誌位已經被設置成false了 。
public final void join()//讓線程加入執行,執行某一線程join方法的線程會被凍結,等待某一線程執行結束,該線程纔會恢復到可運行狀態
public final boolean isAlive() /’di?m?n /
將線程標記爲守護線程(後臺線程):setDaemon(true); 注意該方法要在線程開啓前使用。和前臺線程一樣開啓,並搶資源運行,所不同的是,當前臺線程都結束後,後臺線程會自動結束。無論後臺線程處於什麼狀態都會自動結束。
淺談同步
1.爲什麼需要“線程同步”
線程間共享代碼和數據雖然可以節省系統開銷,提高程序運行效率,但同時也導致了數據的“訪問衝突”問題,如何實現線程間的有機交互、並確保共享資源在某些關鍵時段只能被一個線程訪問,即所謂的“線程同步”(Synchronization)就變得至關重要。
2. 臨界資源
多個線程間共享的數據稱爲臨界資源(Critical Resource),由於是線程調度器負責線程的調度,程序員無法精確控制多線程的交替順序。因此,多線程對臨界資源的訪問有時會導致數據的不一致性即(出現線程的安全隱患)。