
典型回答
Java 提供了不同層面的線程安全支援。在傳統集合架構内部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),我們可以調用 Collections 工具類提供的包裝方法,來擷取一個同步的包裝容器(如 Collections.synchronizedMap),但是它們都是利用非常粗粒度的同步方式,在高并發情況下,性能比較低下。
另外,更加普遍的選擇是利用并發包提供的線程安全容器類,它提供了:
- 各種并發容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。
- 各種線程安全隊列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
- 各種有序容器的線程安全版本等。
具體保證線程安全的方式,包括有從簡單的 synchronize 方式,到基于更加精細化的,比如基于分離鎖實作的 ConcurrentHashMap 等并發實作等。具體選擇要看開發的場景需求,總體來說,并發包内提供的容器通用場景,遠優于早期的簡單同步實作。
高手回答
1.7
put加鎖
通過分段加鎖segment,一個hashmap裡有若幹個segment,每個segment裡有若幹個桶,桶裡存放K-V形式的連結清單,put資料時通過key哈希得到該元素要添加到的segment,然後對segment進行加鎖,然後在哈希,計算得到給元素要添加到的桶,然後周遊桶中的連結清單,替換或新增節點到桶中
size
分段計算兩次,兩次結果相同則傳回,否則對是以段加鎖重新計算
1.8
put CAS 加鎖
1.8中不依賴與segment加鎖,segment數量與桶數量一緻;
首先判斷容器是否為空,為空則進行初始化利用volatile的sizeCtl作為互斥手段,如果發現競争性的初始化,就暫停在那裡,等待條件恢複,否則利用CAS設定排他标志(U.compareAndSwapInt(this, SIZECTL, sc, -1));否則重試
對key hash計算得到該key存放的桶位置,判斷該桶是否為空,為空則利用CAS設定新節點
否則使用synchronize加鎖,周遊桶中資料,替換或新增加點到桶中
最後判斷是否需要轉為紅黑樹,轉換之前判斷是否需要擴容