java多線程(二)

一、控制線程

Java的線程支持提供了一些便捷的工具方法,通過這些便捷的工具方法可以很好的控制線程的執行。

1. join線程
Thread提供了一個讓線程等待另一個線程完成的方法:join()方法。當某個程序執行過程中,調用另外一個線程的join()方法時,調用線程將被阻塞,直到被join方法加入的join線程完成爲止。
join()方法通常由使用線程的程序調用,將大問題劃分成許多小問題,每個小問題分配一個線程,當所有的小問題都得到處理後,再調用主線程進一步操作。

join方法有三種重載形式:

  • join():等待被join的線程執行完成
  • join(long millis):等待被join的線程的時間最長爲millis毫秒,若在millis毫秒內被join的線程還未執行結束,則不再等待
  • join(long millis, int nanos):等待被join的線程的時間最長爲millis毫秒加上nanos微秒。這種方法很少用,因爲程序對時間的精度無須精確到微秒,且操作系統本身也無法精確到微秒。

2. 後臺線程
有一種線程,它在後臺運行,它的任務是爲其他線程提供服務,這種線程被稱爲後臺線程,又稱爲守護線程或精靈線程
JVM的垃圾回收線程就是典型的後臺線程。當所有的前臺線程都死亡的時候,後臺線程會自動死亡。
調用Thread對象的setDaemon(true)方法可以將指定線程設置成後臺線程。
Thread類還提供了isDaemon()方法來判斷指定線程是否爲後臺線程。

注意:在設置某個線程爲後臺線程時,必須在該線程啓動之前設置。

3. 線程睡眠:sleep
Thread類中的sleep方法可以將正在執行的線程暫停一段時間,並進入阻塞狀態。sleep方法有兩種重載的形式:

  • static void sleep(long millis):讓當前正在執行的線程暫停millis毫秒,並進入阻塞狀態
  • static void sleep(long millis, int nanos):讓當前正在執行的線程暫停millis毫秒加nanos微秒。同上,該方法很少用

4. 線程讓步:yeild
yield()方法是和sleep方法相似的方法,它也是Thread類提供的一個靜態方法,它也可以讓當前正在執行的線程暫停,但它不會阻塞該線程,只是將該線程轉入就緒狀態。
實際上,當某個線程調用了yield方法暫停之後,只有優先級與當前線程相同或者優先級比當前線程更高就緒狀態的線程纔會獲得執行機會。

5. 改變線程優先級
每個線程執行時都有一定的優先級,每個線程默認的優先級都與創建它的父線程具有相同的優先級。默認情況下,main線程具有普通優先級,由main線程創建的子線程也有普通優先級。
Thread提供了setPriority(int newPriority)和getPriority()方法來設置和返回指定線程的優先級,其中setPriority方法的參數可以是一個整數,範圍1到10,也可以使用Thread類中的三個靜態常量:
MAX_PRIORITY:其值爲10, MIN_PRIORITY:其值爲1 , NORM_PRIORITY:其值爲5。

二、線程同步

由於系統的線程調度具有一定的隨機性,所以當多個線程來訪問同一個數據時,非常容易出現線程安全問題。

1. 線程安全問題
當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行,導致共享數據的錯誤。

2. 同步代碼塊
出現線程安全問題的原因是run方法的方法體不具有同步安全性,程序中有多條併發線程在操作共享數據,就會出現問題。
爲解決這個問題,Java多線程引入了同步監視器,使用同步監視器的方法就是同步代碼塊。同步代碼塊的語法格式如下:

synchronized(obj){
    ...
    //同步代碼塊
}

上面代碼的含義是,線程開始執行同步代碼塊之前,必須先獲得對同步監視器的鎖定
注意:任何時刻只能有一條線程獲得對同步監視器的鎖定,當同步代碼塊執行結束後,該線程自然釋放對該同步監視器的鎖定。

同步監視器可以阻止兩條線程對同一個共享資源進行併發訪問,因此通常將可能被併發訪問的共享資源當做同步監視器。synchronized代碼塊通常將run方法裏的方法體修改成同步代碼塊,這樣的做法符合“加鎖,修改完成,釋放鎖”的邏輯,任何線程想修改指定資源之前,首先對該資源加鎖,在加鎖期間其他線程無法修改該資源,當該線程修改完成,該線程釋放對該資源的鎖定。這樣就可以保證併發線程在任一時刻只有一條線程可以進入修改共享資源的代碼區。

3. 同步方法
同步方法就是使用synchronized關鍵字來修飾某個方法。同步方法的同步監視器是this。
通過同步方法可以將某類變成線程安全的類,線程安全的類有如下特徵:

  • 該類的對象可以被多個線程安全的訪問
  • 每個線程調用該對象的任意方法之後都將得到正確結果
  • 每個線程調用該對象的任意方法之後,該對象狀態依然保持合理狀態

4. 釋放同步監視器的鎖定
任何線程進入同步代碼塊、同步方法之前,都必須先獲得對同步監視器的鎖定。但是程序無法顯示釋放對同步監視器的鎖定,線程會在以下情況釋放鎖定:

  • 當前線程的同步方法、同步代碼塊執行結束
  • 當線程在同步代碼塊、同步方法中遇到了break、return終止代碼塊、方法的繼續執行
  • 當線程在同步代碼塊、同步方法中出現了未處理的Error或Exception
  • 線程執行同步代碼塊或同步方法時,程序執行了同步監視器的wait()方法

下面情況下,線程不會釋放同步監視器:

  • 線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法來暫停當前線程的執行,當前線程不會釋放同步監視器;
  • 其他線程調用了該線程的suspend方法將該線程掛起,該線程不會釋放同步監視器。應當儘量避免使用suspend和resume方法來控制線程。

5. 同步鎖(Lock)
JDK1.5之後,Java提供了另外一種線程同步的機制:通過顯示定義同步鎖對象來實現同步。通常認爲Lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,Lock實現允許更靈活的結構,可以具有差別很大的屬性,並且可以支持多個相關的Condition對象。
在線程安全的控制中,通常喜歡使用ReentrantLock(可重入鎖)。通常使用Lock對象的代碼格式如下:

class X{
    // 定義鎖對象
    private final ReentrantLock lock = new ReentrantLock();
    public void m(){
        // 加鎖
        lock.lock();
        try{
            // 需要保證線程安全的代碼
        }
        // finally保證釋放鎖
        finally{
            lock.unlock();
        }
    }
}

當獲取了多個鎖的時候,它們必須按照相反的順序釋放,且必須在與所有鎖被獲取時相同的範圍內釋放所有鎖。ReentrantLock鎖具有重入性,就是線程可以對它已經加鎖的ReentrantLock鎖再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock方法的嵌套調用,線程在每次調用lock()加鎖後,必須顯示調用unlock()釋放鎖。

6. 死鎖
當兩個線程相互等待對方釋放同步監視器時就會發生死鎖,一旦出現死鎖,整個程序既不會發生任何異常,也不會給出任何提示,所有線程處於阻塞狀態,無法繼續。
死鎖的描述:假如有2個線程,一個線程想先鎖對象1,再鎖對象2,恰好另外有一個線程先鎖對象2,再鎖對象1。在這個過程中,當線程1把對象1鎖好以後,就想去鎖對象2,但是不巧,線程2已經把對象2鎖上了,也正在嘗試去鎖對象1。
什麼時候結束呢,只有線程1把2個對象都鎖上並把方法執行完,並且線程2把2個對象也都鎖上並且把方法執行完畢,那麼就結束了,但是,誰都不肯放掉已經鎖上的對象,所以就沒有結果,這種情況就叫做線程死鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章