一、線程的概念
一個程序同時執行多個任務,每個任務稱爲一個線程。
進程有自己單獨的變量,線程共享數據。
單核多線程不算真正的多線程,因爲CPU在同一時間只能處理一個線程。
創建線程類的方法:實現Runnable接口或繼承Thread類。
//線程中執行任務過程
class MyRunnable implements Runnable{
//執行run方法中的代碼
public void run(){
task code;
//sleep方法使線程休眠DELAY的秒數,需捕獲異常
Thread.sleep(DELAY);
}
}
//創建類對象
Runnable r = new MyRunnable();
//由Runnable對象創建線程
Thread t = new Thread(r);
//啓動線程
t.start();
sleep方法、wait方法:使線程中斷。
創建新線程調用start方法而不是run方法,否則不能創建新線程,只是執行同一個線程中的任務。
start方法:創建一個執行run方法的新線程。
run方法:被start方法調用後執行方法內代碼。
二、中斷線程
用interrupt方法來強制終止線程。
//應時常檢查當前線程是否被中斷
while(Thread.currentThread().isInterrupted() && ...){
code;
}
若線程被阻塞就無法檢測中斷狀態,產生InterruptedException。
被中斷的線程對中斷有不同的響應,很重要的線程處理異常後繼續執行,不理會中斷。但線程中斷後終止的情況更普遍。
interrupted方法:靜態方法,檢測當前線程是否被中斷,同時會清除該線程中斷狀態。
isInterrupted方法:實例方法,檢驗是否有線程被中斷,不改變中斷狀態。
三、線程的狀態
5種。New(新生),Runnable(可運行),Blocked(被阻塞),Waiting(等待),Terminated(被終止)。
新生線程(New)
new創建新線程後該線程還未開始運行。
可運行線程(Runnable)
調用start方法後線程處於可運行狀態(不一定運行)。即就緒狀態,等待CPU調度後運行。
被阻塞線程(Blocked)和等待線程
處於上述兩種狀態時暫時不活動。
阻塞狀態:線程試圖獲取被其他線程持有的內部的對象鎖時,進入阻塞狀態。其他線程釋放鎖併線程調度器重新調度該線程時,線程變爲非阻塞狀態。
等待狀態:線程在調度器中等待條件時,進入等待狀態。(Object.wait方法,Thread.join方法,等待java.util.concurrent中的Lock或Condition時)
/**
* join方法的含義:使當前進程暫停,等待調用了join方法的線程執行完畢後當前線程再執行。
*/
public class JoinTest {
public static void main(String [] args) throws InterruptedException {
Test t1 = new ThreadJoinTest("aaa");
Test t2 = new ThreadJoinTest("bbb");
t1.start();
// main線程暫停,返回t1線程,t1線程執行完畢後,main線程重新執行
t1.join();
t2.start();
}
}
class Test extends Thread{
public Test(String name){
super(name);
}
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
計時等待:調用有超時參數的方法時導致線程進入計時等待狀態(規定時間內暫停或在被激活前暫停)。
被終止線程
當run方法執行完畢或因爲未捕獲異常而使run方法終止時,線程終止。
四、線程的屬性
線程優先級
線程都有優先級。線程繼承其父線程的優先級。
setPriority:設置優先級:在MIN_PRIORITY(1)與MAX_PRIORITY(10)之間,NORM_PRIORITY爲5。調度器先選擇有高優先級的線程,如果在低優先級線程執行的過程中出現高優先級線程,則暫停執行低優先級線程,去執行高優先級線程。
yield:靜態方法,使當前線程處於讓步狀態。當前線程將自己對CPU的佔用讓給同優先級的其他線程,但這是概率問題,並不確定結果。
守護線程
SetDaemon(true):將線程轉換爲守護線程。
守護線程用途:爲其他線程提供服務(例如計時)。當只剩守護線程時虛擬機退出。
未捕獲異常處理器
run方法中的不被檢測的異常在線程死亡前被傳遞到用於未捕獲異常的處理器(實現Thread.UncaughtExceptionHandler接口類)。
setUncaughtExceptionHandler方法爲線程安裝默認處理器。若不安裝,處理器爲線程的ThreadGroup對象。
五、同步
競爭條件:多個線程共享數據,同時對數據進行操作時,容易互相覆蓋操作導致結果出錯。
解決方法:各線程執行過程中不被中斷則不會出現錯誤。
鎖對象
防止代碼塊受併發訪問干擾:synchronized關鍵字,ReentrantLock類(可重入鎖)。
//任何時刻只有一個線程進入臨界區,
//一個線程封鎖了鎖對象,其他任何線程都無法調用lock(被阻塞)
//直到線程釋放了鎖對象時才能停止阻塞
//該類的每個對象都有一個自己的鎖對象,若兩個線程分別訪問不同的鎖對象,則不會阻塞。
public class Bank{
private Lock myLock = new ReentrantLock();
public void transfer(){
myLock.lock();//ReentrantLock對象
try{
...
}
finally{
myLock.unlock();//鎖必須被釋放,所以放在finally子句中
}
}
}
Reentrant鎖可被用來保護臨界區,是可重入的。線程可重複獲得已經持有的鎖(線程在已經獲得鎖對象後可以進入與它同步的其他代碼塊,再次獲得自己已經持有的鎖。比如在同一個類中,有鎖的方法內幾次調用自身或其他有鎖的方法)。鎖在被重複調用時計數,調用就++,釋放就–,當計數爲0時釋放鎖。
ReentrantLock(boolean fair):構建一個帶有公平策略的鎖。公平鎖對等待時間長的線程優先,但會降低性能,也無法保證完全公平,所以一般不強制使用。
條件對象
條件對象:管理已經獲得鎖但不能運行的線程(進入臨界區後必須滿足條件才能執行)。
鎖可以擁有一個或多個相關的條件對象。
比如賬戶中沒有足夠餘額時需要另一個線程向賬戶中注入資金,但這一線程獲取了鎖且沒有釋放,其他線程無法操作。此時需要條件對象。
class Bank{
public Bank(){
...
//鎖對象獲取條件對象,命名爲“餘額充足”
sufficientFunds = bankLock.newCondition();//給條件對象賦值
}
...
private Condition sufficientFunds;//定義條件對象
}
線程擁有條件鎖時,僅可以調用如下方法:
await方法:如果當前進程負責轉賬的方法發現餘額不足無法轉賬,則調用sufficientFunds.await(),使當前進程被阻塞,放棄鎖。此時另一個線程開始轉賬。
while(不能繼續執行){
condition.await();
}
signalAll方法:通知等待的線程可能滿足條件,需要再次檢測。解除所有等待線程的阻塞。
另一個線程轉賬完成後,調用sufficientFunds.signalAll(),激活之前因爲調用了await方法而進入條件的等待集的線程,讓它們成爲可運行的。被激活的其中一個線程重新返回調用await處,獲得鎖,繼續執行,並再次測試條件。
signal方法:隨機解除處於等待集中的某個線程的阻塞狀態。易發生死鎖:隨機選擇的線程可能不能運行,再次阻塞。
synchronized關鍵字
Java中每個對象都有一個內部鎖,如果方法用synchronized關鍵字聲明,則對象的鎖保護整個方法。(獲取內部鎖才能調用該方法)
wait方法:添加線程到等待集中,相當於condition.await()方法。
notify / notifyAll方法:解除等待線程的阻塞狀態,相當於condition.signalAll方法。
以上三個方法爲Object類的final方法。
儘量使用synchronized關鍵字,會減少編碼數量與出錯機率。
每個Java對象有一個鎖,線程可調用同步方法或進入一個同步阻塞來獲得鎖。
synchronized(obj){
臨界區;//獲得obj的鎖
}
------------------------------------------------------------
//特殊的鎖
public class Bank{
public void transfer(){
synchronized(lock);//lock的作用僅爲創建每個對象都擁有的鎖
{
...
}
}
private Object lock = new Object();
}