1. 程式人生 > >第一部分 基礎知識 第2章:執行緒安全性

第一部分 基礎知識 第2章:執行緒安全性

       要編寫執行緒安全的程式碼,其核心是對狀態訪問進行管理,特別是對共享的(Shared)可變的(Mutable)狀態進行管理。

物件的狀態是指:儲存在狀態變數中的資料(指可影響任何外部可見行為的資料),還可能包括其他依賴物件的域。

       當多個執行緒訪問一個可變的狀態變數時沒有使用合適的同步,那麼程式就會出現錯誤。三種方式修正這個錯誤:

       ① 將可變狀態變數轉換為不可變的變數

       ② 不線上程之間共享該狀態變數

       ③ 在訪問狀態變數時使用同步

       執行緒安全的程式並非一定是由執行緒安全類構成的,執行緒安全類中也可以包含非執行緒安全類。

???

 2.1 什麼是執行緒安全性

定義:當多個執行緒訪問某個類時,該類始終表現出正確性,那麼該類就是執行緒安全的。

           在一個執行緒安全類中實現了必要的同步機制,因此客戶端無需進一步採取措施。

       

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

無狀態:既不包含任何域,也不包含任何對其他類中域的引用。計算過程中的臨時狀態只能存放於執行緒棧上的區域性變數,並且只能有正在執行的執行緒訪問。

2.2 原子性

++count是一個非原子性操作,它由三個狀態“讀取-修改-寫入”構成,最終的狀態由讀取的之前的狀態決定。

在併發過程中,由於不正確的執行順序導致不正確的結果的發生的現象叫做:競態條件

2.2.1 競態條件

“先檢查-後執行”:就是指檢查過的結果在執行時已經失效,因為在檢查過和執行之間的時間會有新事件發生。

2.2.2 延遲初始化(先檢查後執行)

假設兩個執行緒同時呼叫 getInstance(),會出現競態條件。

2.2.3 符合操作

       以上例子表明,要避免競態條件,就必須在某個執行緒修改變數時,阻止這個操作,防止其他執行緒使用這個變數,從而確保該訪問過程出現在修改前或者修改後,而不是修改中。

      為了確保執行緒安全性,“先檢查,後執行”(延遲初始化)和“讀取-修改-刪除”(count++)等操作必須是原子的。

      

   通過AtomicLong 來代替 long型別的計數器,能夠確保所有對計數器狀態的訪問狀態都是原子性的。

   在一個無狀態的類中新增一個狀態時,如果該狀態是由執行緒安全物件管理的,那麼該類任然是執行緒安全的。

   要儘可能用執行緒安全物件。

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

未完待續...