第一部分 基礎知識 第2章:執行緒安全性
要編寫執行緒安全的程式碼,其核心是對狀態訪問進行管理,特別是對共享的(Shared)、可變的(Mutable)狀態進行管理。
物件的狀態是指:儲存在狀態變數中的資料(指可影響任何外部可見行為的資料),還可能包括其他依賴物件的域。
當多個執行緒訪問一個可變的狀態變數時沒有使用合適的同步,那麼程式就會出現錯誤。三種方式修正這個錯誤:
① 將可變狀態變數轉換為不可變的變數
② 不線上程之間共享該狀態變數
③ 在訪問狀態變數時使用同步
執行緒安全的程式並非一定是由執行緒安全類構成的,執行緒安全類中也可以包含非執行緒安全類。
???
2.1 什麼是執行緒安全性
定義:當多個執行緒訪問某個類時,該類始終表現出正確性,那麼該類就是執行緒安全的。
在一個執行緒安全類中實現了必要的同步機制,因此客戶端無需進一步採取措施。
無狀態的物件一定是執行緒安全的
無狀態:既不包含任何域,也不包含任何對其他類中域的引用。計算過程中的臨時狀態只能存放於執行緒棧上的區域性變數,並且只能有正在執行的執行緒訪問。
2.2 原子性
++count是一個非原子性操作,它由三個狀態“讀取-修改-寫入”構成,最終的狀態由讀取的之前的狀態決定。
在併發過程中,由於不正確的執行順序導致不正確的結果的發生的現象叫做:競態條件
2.2.1 競態條件
“先檢查-後執行”:就是指檢查過的結果在執行時已經失效,因為在檢查過和執行之間的時間會有新事件發生。
2.2.2 延遲初始化(先檢查後執行)
假設兩個執行緒同時呼叫 getInstance(),會出現競態條件。
2.2.3 符合操作
以上例子表明,要避免競態條件,就必須在某個執行緒修改變數時,阻止這個操作,防止其他執行緒使用這個變數,從而確保該訪問過程出現在修改前或者修改後,而不是修改中。
為了確保執行緒安全性,“先檢查,後執行”(延遲初始化)和“讀取-修改-刪除”(count++)等操作必須是原子的。
在一個無狀態的類中新增一個狀態時,如果該狀態是由執行緒安全物件管理的,那麼該類任然是執行緒安全的。
要儘可能用執行緒安全物件。
2.3 加鎖機制
仿照上例:在Servlet中新增更多的狀態是否只需要新增更多的執行緒安全物件就足夠了。
不正確:
儘管這些原子引用本身是執行緒安全的,但在此類中存在競態機制,
2.3 加鎖機制
仿照上例:在Servlet中新增更多的狀態是否只需要新增更多的執行緒安全物件就足夠了。
不正確:
儘管這些原子引用本身是執行緒安全的,但在此類中存在競態機制,因此也不是執行緒安全的類。
當不變性條件下涉及多個變數時,各個變數間不是彼此獨立的,某個變數的值會約束到其他變數的值。因此,當一個變數發生變化時,需要在一個原子操作中對其他變數進行操作。
set方法,get方法應保證在number和factor之間的原子性,get要原子內兩者都get,set要原子內set。
要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變數。
2.3.1 內建鎖
Synchronized (lock){
//訪問或修改由鎖保護的共享狀態
}
每個Java物件都可以做一個實現同步的鎖,這些鎖被稱為內建鎖或者監視器鎖。執行緒在進入同步程式碼塊時會自動獲取鎖,退出程式碼塊時會自動釋放鎖。
java中的鎖相當於一種互斥體,也就是隻有一個執行緒能擁有這個鎖。當A企圖獲取B持有的鎖時,A必須阻塞知道B釋放,若B不釋放,A將一直阻塞下去。
雖然執行緒安全,但是效能太低,因為同一時刻只能有一個執行緒執行service
未完待續...