1. 程式人生 > >《Java併發程式設計實戰》—— 第二章 執行緒安全性

《Java併發程式設計實戰》—— 第二章 執行緒安全性

編寫執行緒安全的程式碼,核心在於要對狀態訪問操作進行管理,特別是對共享的和可變的狀態的訪問。

物件的狀態

同步機制:

  • synchronized(獨佔的加鎖方式)
  • volatile
  • 顯式鎖
  • 原子變數

多個執行緒訪問同一個變數時,有3種方式保障安全:

  • 不線上程之間共享該狀態變數
  • 將狀態變數設計為不可變的變數
  • 在訪問狀態變數時使用同步

程式狀態的封裝性越好,就越容易實現程式的執行緒安全性,並且程式碼的維護人員也越容易保持這種方式:

  • 良好的面向物件技術
  • 不可修改性
  • 明晰的不變性規範

執行緒安全性
當多個執行緒訪問某個類時,不管執行時環境採用何種排程方式或者這些執行緒將如何交替執行,並且在主調程式碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那麼就稱這個類是執行緒安全的。

無狀態物件一定是執行緒安全的。

原子性

競態條件:由於不恰當的執行時序而出現不正確的結果。
當某個計算的正確性取決於多個執行緒的交替執行時序時,那麼就會發生競態條件。大多數競態條件的本質是,基於一種可能失效的觀察結果來做出判斷或者執行某個計算。如:
先檢查後執行
讀取-修改-寫入

當在無狀態的類中新增一個狀態時,如果該狀態完全由執行緒安全的物件來管理,那麼這個類仍然是執行緒安全的。
在java.util.concurrent.atomic包中包含了一些原子變數類,用於實現在數值和物件引用上的原子狀態轉換。
在實際情況中,應儘可能使用現有的執行緒安全物件來管理類的狀態。

當在不變性條件中涉及多個變數時,各個變數之間並不是彼此獨立的,而是某個變數的值會對其他變數的值產生約束。因此,當更新某一個變數時,需要在同一個原子操作中對其他變數同時進行更新。
要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變數。

內建鎖(同步程式碼塊):synchronized
鎖就是方法呼叫所在的物件。靜態方法以Class物件作為鎖。
是一種互斥鎖,最多隻有一個執行緒能持有這種鎖。其他執行緒必須等待或者阻塞。
重入意味著獲取鎖的操作粒度是“執行緒”而不是“呼叫”。
如果用同步來協調對某個變數的訪問,那麼在訪問這個變數的所有位置上都需要使用同步。而且,當使用鎖來協調對某個變數的訪問時,在訪問變數的所有位置上都要使用同一個

鎖。
當獲取與物件關聯的鎖時,並不能阻止其他執行緒訪問該物件,而是阻止其他執行緒獲得同一個鎖。
每個共享的和可變的變數都應該只由一個鎖來保護,從而使維護人員知道是哪一個鎖。
一種常見的加鎖約定是,將所有的可變狀態都封裝在物件內部,並通過物件的內建鎖對所有訪問可變狀態的程式碼路徑進行同步,使得在該物件上不會發生併發訪問。
在不變性條件中的每個變數都必須由同一個鎖來保護。
synchronized方法可以確保單個操作的原子性,但如果要把多個操作合併為一個複合操作,還是需要額外的加鎖機制。

效能:
縮小同步程式碼塊的範圍,儘量將不影響共享狀態且執行時間較長的操作從同步程式碼塊中分離出去。
此外,在獲取與釋放鎖等操作上都需要一定的開銷,因此如果將同步程式碼塊分解得過細也不好。
當執行時間較長的計算或者可能無法快速完成的操作時(例如網路I/O或控制檯I/O),一定不要持有鎖。