同步容器類的問題
- 整個容器類加鎖,線性訪問容器實例,併發性能非常低
- 雖然單個操作是線程安全的,但是複合操作如果不另外加鎖,本身無法保證併發安全
- 迭代器迭代過程中,如果發生元素的操作,會觸發ConcurentModificationException異常,使用了“及時失敗”機制
ConcurrentHashMap的優化手段
- 不是在每個方法上都在同一個鎖上同步並使得每次只能有一線程訪問容器,而是使用一種粒度更細的加鎖機制來實現更大程序的共享,這種機制稱爲分段鎖。這種機制中,任意數量的讀取線程可以併發地訪問Map,執行讀取操作的線程和執行寫入操作的線程可以併發地訪問Map,並且一定數量的寫入線程可以併發地修改Map。
- 迭代器不會拋出ConcurrentModificationException,因此不需要在迭代過程中對容器加鎖。迭代器具有弱一致性,而並非“及時失敗”。
- 對於一些需要在整個Map上進行計算的方法,例如size/isEmpty,這些方法的語義被略微減弱了以反映容器的併發特性。
- 添加了額外的複合原子操作【沒有才插入、映射到值了才移除、映射到舊值才替換、映射到某個值時才替換到新值】
CopyOnWriteArrayList
原理
使用場景
阻塞隊列與生產-消費模式
問題
解決方法
串行線程封閉
優點
對於可變對象,生產者-消費者這種設計與阻塞隊列一起,促進了串行線程封閉,從而將對象所有權從生產者交付給消費者。
線程封閉對象只能由單個線程擁有,通過安全地發佈該對象“轉移”所有權,實現了轉移前由前一線程獨佔,轉移後由後一線程獨佔。
實現方法
阻塞隊列使得這種線程封閉的所有權轉移變得容易,其次還可以通過ConcurrentMap的原子方法remove或者AtomicReference的原子方法compareAndSet來完成這項工作。
閉鎖(CountDownLatch)
使用場景
- 確保某個計算在其需要的所有資源都被初使化之後才繼續執行。
- 確保某個服務在其依賴的所有其它服務都已經啓動之後才啓動。
- 等待直到某個操作的所有參與者(如多玩家遊戲中的所有玩家)都就緒再繼續執行。
使用方法
FutureTask也可以用作閉鎖
信號量的作用
柵欄
柵欄與閉鎖的關鍵區別在於,所有線程必須都同時達到柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其它線程。
Exchanger
是一種兩方柵欄,各方在柵欄位置上交換數據。如果一方通過exchange方法申請交換,而另一方還沒有來,則會一直阻塞直至對方也提出申請。
最簡單的方案是,當緩衝區被填滿時,由填充任務進行交換,當緩衝區爲空時,由清空任務進行交換。但是如果新數據的到達率不可預測,那麼一些數據的處理過程就將延遲。另一個方法是,不僅當緩衝被填滿時進行交換,並且當緩衝被填充到一定程序並保持一定時間後,也進行交換。