Java多線程,java線程
1.線程的建立與啟動在Java中,多線程的實現有兩種方式:
繼承java.lang.Thread類
實現java.lang.Runnable介面
1.1繼承Thread類建立線程繼承Thread類建立並啟動線程的步驟:
1、定義Thread的子類,並重寫該類的run()方法,run()方法的方法體就表示線程需要完成的任務。run()被稱為線程執行體。
2、建立Thread的子類的執行個體,即建立線程對象。
3、調用線程對象的start()方法啟動該線程。
1.2實現Runnable介面建立線程實現Runnable介面建立並啟動線程的步驟:
1、定義Runnable介面的實作類別,並重寫該介面的run()方法,run()方法的方法體就表示線程需要完成的任務。run()被稱為線程執行體。
2、建立Runnable的實作類別的執行個體,並以此執行個體作為Thread的target來實現Thread對象,該Thread對象才是真正的線程對象。
3、調用線程對象的start()方法啟動該線程。
PS:使用Runnable建立的多個線程對象可以共用Runnable的實作類別的執行個體屬性。
2.線程的生命週期線上程的生命週期中有五種狀態。
2.1建立狀態和就緒狀態當程式使用new關鍵字建立一個線程之後,該線程就處於建立狀態。
當線程對象調用了start()方法之後,該線程就處於就緒狀態。處於就緒狀態的線程並沒有開始運行,只是可以運行而已,,至於該線程何時開始運行就取決於JVM裡線程調度器的調度。
PS:不能直接調用線程的run()方法,不然就變成了普通方法的調用,而不是執行線程執行體。
2.2運行狀態和阻塞狀態處於就緒狀態的線程獲得了CPU,開始執行run()方法的方法體,則該線程處於運行狀態。
當一個線程開始運行後,在某個時刻失去了CPU,則該線程處於阻塞狀態。
當發生如下情況時,線程會進入阻塞狀態(從運行狀態轉入阻塞狀態):
1、線程調用sleep()方法主動放棄佔用的處理器資源
2、線程調用了一個阻塞式IO方法
3、線程試圖擷取一個同步監視器,但是該同步監視器正被其他線程所擁有。
4、線程在等待某個通知
5、線程調用suspend()方法將該線程掛起。這個方法容易造成死結。
針對上面幾種情況,當發生如下情況時可以解除阻塞(從阻塞狀態轉入就緒狀態):
1、調用sleep()方法的線程過了指定的時間
2、線程調用的阻塞式IO方法已經返回
3、線程成功擷取它試圖擷取同步監視器
4、線程正在等待某個通知時,其他線程發出了一個通知
5、處於掛起狀態的線程調用了resume()方法
PS:線程調用yield()方法可以從運行狀態轉入就緒狀態
2.3死亡狀態線程會以三種方式結束,結束後就處於死亡狀態:
1、線程的run()執行完成,線程正常結束
2、線程拋出一個未捕獲的Exception或Error
3、線程調用stop()方法來結束該線程,該方法容易造成死結
為了測試某個線程是否死亡,可以調用線程對象的isAlive()方法,當線程處於就緒,運行,阻塞三種狀態時,返回true,當線程處於建立,死亡兩種狀態時,返回false。
3.線程式控制制 Java的線程提供一些便捷的工具,通過這些工具可以很好地控制線程的執行。
3.1join線程Thread提供讓一個線程等待另一個線程完成的方法——join()方法。當在某個線程的執行過程中調用另一個線程的join()方法時,調用線程將被阻塞,直到被join()方法的線程執行完為止。
3.2後台線程有一種線程是在後台啟動並執行,它的任務是為其他線程提供服務,這種線程叫做後台線程(Daemon Thread),又稱為守護線程或精靈線程。
後台線程有個十分重要的特點:如果所有的前台線程死亡,那麼後台線程自動死亡。
調用線程對象的setDaemon(true)方法可將指定線程設定為後台線程。
Thread類還提供一個isDaemon()方法,用來判斷指定線程是不是後台線程,是後台線程就返回true,否則返回false。
主線程(main線程)預設是前台前程。
前台,後台線程有一個重要的特點:前台線程建立的子線程預設是前台線程,後台線程建立的子線程預設是後台線程。
PS:setDaemon(true)設定線程為後台線程必須在start()方法之前調用。
3.3線程睡眠如果需要讓當前正在後執行的線程暫停一段時間,並進入阻塞狀態,可以通過調用Thread類的靜態sleep()方法實現。
3.4線程讓步yield()方法是一個和sleep()方法相似的方法,它也是Thread類的一個靜態方法,它也可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是讓該線程轉入就緒狀態。
當某個線程調用了yield()方法暫停後,只有優先順序與該線程相同,或者優先順序高於該線程的處於就緒狀態的線程才會獲得執行的機會。
3.5線程的優先順序每個線程都具有一定的優先順序,優先順序高的線程有更多的機會獲得執行,優先順序低的有更少的機會獲得執行。
主線程(main線程)預設優先順序是普通優先順序,每個線程的預設優先順序都與建立它的父線程相同。
Thread類提供setPriority(int newPriority)方法和getPriority()方法來設定和擷取指定線程的優先順序,其中setPriority(int newPriority)方法中的參數取值範圍是1-10,也可以使用Thread類的三個靜態常量
MAX_PRIORITY:值是10
MIN_PRIORITY:值是1
NORM_PRIORITY:值是5
PS:Java提供10個線程優先順序,但不是所有的作業系統都支援這10個優先順序,所以我們應該盡量避免直接有數字來設定優先權,應該多用三個靜態常量來設定優先權,這樣程式的移植行更好。
4.線程同步4.1安全執行緒問題多個線程同時訪問一個對象時,可能會出現安全執行緒問題
4.2同步監視器為瞭解決安全執行緒問題,Java的多線程支援引入了同步監視器。
4.2.1同步代碼塊使用同步監視器的一個方法是同步代碼塊。
synchronized(obj){
...//此處代碼就是同步代碼塊
}
synchronized後面圓括弧裡面的obj就是同步監視器,上面代碼的含義是:線程開始執行同步代碼塊之前,必須先獲得對同步監視器的鎖定。
任何時刻只能有一個線程擷取對同步監視器的鎖定。
4.2.2同步方法使用同步監視器的一個方法是同步方法。
對同步方法而言,無須顯示地指定同步監視器,同步方法的同步監視器是this,也就是同步方法本身。
public synchronized void play(){
...//同步方法體
}
使用同步方法可以非常方便地實現安全執行緒類,安全執行緒類具有以下特徵:
1、該類的對象可以被多個安全執行緒訪問
2、每個線程調用該對象的任意方法後將得到正確的結果
3、每個線程調用該對象的任意方法後,該對象依然保持合理狀態
4.2.3釋放同步監視器的鎖定釋放同步監視器的鎖定的方法:
1、當前線程的同步方法、同步代碼塊執行結束
2、當前線程的同步方法、同步代碼塊中遇到break、return終止了該方法、該代碼塊的繼續執行
3、當前線程的同步方法、同步代碼塊中出現了未處理的Error或Exception,導致該方法、該代碼塊異常結束
4、當前線程執行同步方法、同步代碼塊時,程式執行了同步監視器對象的wait()方法,則當前線程暫停
如遇到以下幾種情況,線程不會釋放同步監視器
1、當前線程執行同步方法、同步代碼塊時,程式調用Thread.sleep()、Thread.yield()方法來暫停當前線程的執行
2、當前線程執行同步代碼塊時,其他線程調用該線程的suspend()方法將該線程掛起。這個方法容易造成死結。
4.3同步鎖從Java5開始,Java提供了一種功能強大的心愛你策劃給你同步機制——通過顯示定義同步鎖對象來實現同步,在這種機制下,同步鎖使用Lock對象充當。
Lock提供比同步方法、同步代碼塊更廣泛的鎖定操作,Lock實現允許更靈活的結構,可以具有差別很大的屬性,並且支援多個多個相關的Condition對象。
Lock是充值多線程對共用資源進行訪問的工具。通常,鎖提供了對共用資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共用資源之前應先獲得Lock對象。
某些鎖可能允許對共用資源並發訪問,如ReadWriteLock(讀寫鎖)。
Lock、ReadWriteLock是Java5提供的兩個根介面,並為Lock提供了ReentrantLock(可重新進入鎖)實作類別,為ReadWriteLock提供了ReentrantReadWriteLock實作類別。
使用ReentrantLock的常用格式如下
import java.util.concurrent.locks.ReentrantLock;public class LockTest {private final ReentrantLock lock=new ReentrantLock();public void play(){lock.lock();try{//需要保證安全執行緒的代碼}finally{lock.unlock();}}}
ReentrantLock鎖具有可重新進入性,也就是說,一個線程可以對已經被加鎖的ReentrantLock鎖再次加鎖,一段被鎖保護的代碼可以調用另一個被相同鎖保護的方法。
4.4死結當兩個線程互相等待對方釋放同步監視器時,就會出現死結。所以多線程編程時要採取措施避免死結。
5.線程通訊當線程在系統內運行時,線程的調度具有一定的透明性,程式通常無法準確控制線程的輪換執行,但我們可以通過一些機制來保證線程協調運行。
5.1傳統的線程通訊為了實現線程間的通訊,可以藉助Object類提供的wait()、notify()、notifyAll()3個方法,但這3個方法必須有同步監視器對象來調用。
1、對於使用synchronized修飾的同步方法,因為該類的預設執行個體(this)就是同步監視器,所以可以在同步對象中直接調用這3個方法
2、對於使用synchronized修飾的同步代碼塊,同步監視器是synchronized後括弧裡的對象,所以必須使用該對象調用這3個方法
關於這3個方法的含義
1、wait()方法:導致當前線程等待,知道其他線程調用該同步監視器的notify()方法或notifyAll()方法
2、notify()方法:喚醒在此同步監視器上等待的單個線程
3、notifyAll()方法:喚醒在此同步監視器上等待的所有線程
5.2使用Condition控制線程通訊如果程式不使用synchronized關鍵字來保證同步,而直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器,也不能使用前面的3個方法來進行通訊了。
當使用Lock對象來保證同步時,Java提供一個Condition類來保證協調,使用Condition可以讓那些得到Lock對象卻無法繼續執行的線程釋放Lock對象,Condition對象也可以喚醒其他處於等待的線程。
這時,Lock替代了同步方法或同步代碼塊,Condition替代了同步監視器的功能。
Condition執行個體被綁定在一個Lock對象上。要擷取特定Lock執行個體的Condition執行個體,調用Lock對象的newCondition方法即可。Condition提供了如下3個方法。
1、await()方法:類似於隱式同步監視器上的wait()方法,導致當前小暱稱等待,直到其他線程調用該Condition的signal()方法或signalAll()方法來喚醒該線程
2、signal()方法:喚醒在此Lock對象上等待的單個線程
3、signalAll()方法:喚醒在此Lock對象上等待的所有線程
5.3使用阻塞隊列控制線程通訊Java5提供了一個BlockingQueue介面,雖然它也是Queue的子介面,但它的主要作用不是作容器,而是作為線程同步的工具。BlockingQueue具有一個特徵:當生產者線程試圖向BlockingQueue中加入元素時,如果隊列已滿,則該線程被阻塞;當消費者線程試圖從BlockingQueue中取出元素時,如果隊列已空,則該線程被阻塞。程式的兩個線程通過交替向BlockingQueue中放入元素、取出元素,就可以很好地控制線程的通訊了。
BlockingQueue提供如下兩個支援阻塞的方法,
1、put(E e)方法:嘗試把E元素放入BlockingQueue中
2、take()方法:嘗試從BlockingQueue的頭部取出元素