1. 程式人生 > >Java線程與並發編程實踐----同步

Java線程與並發編程實踐----同步

程序 同時 問題 應該 沒有 通過 餓死 繼續 順序

上一節我們知道了java如何創建線程並啟動,當線程之間沒有交互,程序開發就十分簡單了,但如果線程之間發生交互,通過共享變量的方式

進行交互,就會引發很多線程不安全問題,如,競態條件數據競爭以及緩存變量

競態條件:當計算的正確性取決於相對時間或者調度器所控制的多線程交叉時,競態條件就會發生。如下例子:

if(a == 10.0){
    b = a / 2.0;
}

假如一條線程已經執行完了if(a == 10.0),突然被調度器所停止,另一條線程開始執行,並且修改a = 20,此時繼續執行第一條線程,則b = 10。

這就產生了問題

數據競爭:數據競爭指的是兩條或兩條以上的線程並發的訪問同一塊內存區域,同時其中至少有一條是為了寫,而且這些線程沒有協調對那塊內存

域的訪問。當滿足這些條件的時候,訪問順序就是不確定的。依據這種順序,每次運行都可能會產生不同的結果

緩存變量:為了提升性能,編譯器Java虛擬機以及操作系統會協調在寄存器中或處理器緩存中緩存變量,而不是依賴主存,每條線程都會有其自己的

變量拷貝。當線程寫入這個變量的時候,其實是寫入自己的拷貝;其他線程不太可能看到自己的變量拷貝發生更改。

同步可以解決以上問題,方式有同步代碼塊和同步方法。

//同步代碼塊
synchronized(lock){
    //需要同步的代碼
}
//同步方法
public synchronized void method(){
    //需要同步的代碼
}

使用synchronized

進行同步,它具有互斥性可見性。互斥性即為每條線程對臨界區的訪問都是互斥的,可見性指線程進入臨界區後,都是從主存中讀取

變量的值,離開時又將變量的值寫入主存,因此,他總能看到共享變量最近的修改。而同步是通過監聽器實現的,,每一個Java對象都和一個監聽器相關聯,這樣線

程就可以通過釋放和獲取監聽器的鎖來上鎖和解鎖,而一個線程只能持有一個鎖,以此來實現同步。但是鎖的使用雖然可以實現同步,但也會面臨死鎖活鎖餓死

的挑戰。

死鎖:線程1等待線程2互斥持有的資源,而線程2也在等待線程1互斥持有的資源,兩個線程都無法繼續執行

活鎖:一個線程持續重試一個失敗的操作,無法繼續執行

餓死:一個線程一直被調度器延時訪問其賴以執行的資源。無法繼續執行

Java語言和JVM並未提供避免以上問題的方式以及差錯辦法,因此主要靠我們在編程過程中自己註意,最簡單的就是盡量減少同步方法、同步塊之間的相互調用

Java中還提供了一種更弱的僅僅包含可見性的同步形式,即volatile關鍵字,有的情況下,我們只需要關註代碼的可見性問題,而不在乎他的互斥性,此時使用synchronized

就顯得沒有必要,我們應該考慮使用volatile,使用volatile標記的屬性,線程在訪問他時不會讀取緩存變量的數據,而是從主存中讀取。還有值的註意的是,使用volatile

修飾double和long類型時,應該避免在32位的操作系統上這樣做,因為double、long是8個字節64個bit,在32位操作系統上它的讀取分為兩步,每一步取32位的數據,

volatile保證了可見性,但不能保證操作的原子性,因此應該註意編碼。使用volatile時不能與final進行連用,因為final本來就可以確保線程訪問的安全性,它修飾一個

屬性,屬性的引用不能被修改,而且引用也不能被緩存。


Java線程與並發編程實踐----同步