1. 程式人生 > >《Java併發程式設計實戰》—— 第三章 物件的共享

《Java併發程式設計實戰》—— 第三章 物件的共享

synchronized的兩個作用:原子性和記憶體可見性。

  • 在沒有同步的情況下,編譯器、處理器以及執行時等都可能對操作的執行順序進行一些意想不到的調整(重排序)。
  • 失效資料
  • 非原子的64位操作

加鎖的含義不僅僅侷限於互斥行為,還包括記憶體可見性,為了確保所有執行緒都能看到共享變數的最新值,所有執行讀操作或者寫操作的執行緒都必須在同一個鎖上同步。

volatile

  • 不進行重排序。
  • 不會被快取在暫存器或者對其他處理器不可見的地方。
  • 不加鎖,不阻塞,輕量級。

正確使用方式:確保它們自身狀態的可見性,確保它們所引用物件的狀態的可見性,以及標識一些重要的程式生命週期事件的發生。

加鎖機制既可以確保可見性又可以確保原子性,而volatile變數只能確保可見性。

當且僅當滿足以下所有條件時,才應該使用volatile變數:

  • 對變數的寫入操作不依賴變數的當前值,或者你能確保只有單個執行緒更新變數的值。
  • 該變數不會與其他狀態變數一起納入不變性條件中。
  • 在訪問變數時不需要加鎖。

釋出

逸出:某個不該釋出的物件被髮布了

釋出某個物件時,會間接釋出其他物件:

  • 如map、數組裡面儲存的物件
  • 被髮布的物件的非私有域中引用的物件
  • 可以通過非私有的變數引用和方法呼叫到達的其他物件
  • 一個物件傳遞給了“外部方法”(公有方法或者可以被繼承的方法)
  • 內部類例項裡面隱式的this引用

當且僅當物件的建構函式返回時,物件才處於可預測的和一致的狀態。
所以,不要在構造過程中使this引用逸出。如,在建構函式中啟動一個執行緒。或者在建構函式中呼叫一個可改寫的例項方法時(既不是私有方法,也不是final方法),同樣會導致this引用在構造過程中逸出。

執行緒封閉

一種避免使用同步的方式就是不共享資料。
將某個物件封閉在一個執行緒中。

  • Ad-hoc執行緒封閉技術
  • 棧封閉:區域性變數
  • ThreadLocal類:類似於全域性變數,降低程式碼的可重用性,並在類之間引入隱含的耦合性,因此在使用時要格外小心。

不變性

滿足同步需求的另一種方法是使用不可變物件。
不可變物件一定是執行緒安全的。

滿足以下條件時,物件才是不可變的:

  • 物件建立以後其狀態就不能修改
  • 物件的所有域都是final型別
  • 物件是正確建立的(建立期間,this引用沒有逸出)

除非需要某個域是可變的,否則應將其宣告為final。

對於在訪問和更新多個相關變數時出現的競態條件問題,可以通過將這些變數全部儲存在一個不可變物件中來消除。要更新這些變數,可以建立一個新的容器物件,但其他使用原有物件的執行緒仍然會看到物件處於一致的狀態。

可變物件必須通過安全的方式來發布,這通常意味著在釋出和使用該物件的執行緒時必須使用同步。

一個正確構造的物件可以通過以下方式來安全地釋出(只是釋出,不包含共享):

  • 在靜態初始化函式中初始化一個物件引用
  • 將物件的引用儲存到volatile型別的域或者AtomicReference物件中
  • 將物件的引用儲存到某個正確構造物件的final型別域中
  • 將物件的引用儲存到一個由鎖保護的域中