一次“面試翻車”后的思考:HashMap 和 ConcurrentHashMap 的區(qū)別到底在哪里?
引言
嘿,大家好,我是小米,一個愛研究技術、也愛講故事的 29 歲大哥哥。
上周我接了一個社招面試,面試官直接拋過來一個問題:“說說 Java 里的 HashMap 和 ConcurrentHashMap 有什么區(qū)別?”我心里一緊,心想這不是基礎題嘛,結果我開口解釋了半天,面試官的眼神卻越來越微妙……嗯,這次面試翻車了。
所以,今天我決定好好梳理一下 HashMap 和 ConcurrentHashMap 的區(qū)別,希望我的教訓能幫到大家!
開場故事:為什么需要 ConcurrentHashMap?
先想象一個場景:你和朋友們在餐廳點菜,每個人都可以隨時往菜單里加菜。
問題來了:如果兩個人同時修改菜單,服務員可能會拿到一份有問題的訂單,比如一道菜被重復記錄,或者有的菜根本沒加上。
在單線程中,HashMap 這個"菜單"工作得很好。但到了多線程環(huán)境中,問題就來了:它本身不是線程安全的,多個線程同時操作會導致數(shù)據不一致。
于是,Java 提供了一個改進版的“菜單”——ConcurrentHashMap。不僅線程安全,還能保持一定的性能。
第一回合:結構上的對比
1. HashMap 的結構
HashMap 的底層是由數(shù)組和鏈表組成的,Java 8 以后為了提升性能,又在鏈表長度超過一定閾值時將鏈表轉換為紅黑樹。
它的默認容量是 16,每次擴容時會翻倍到 32、64……以此類推。
2. ConcurrentHashMap 的結構
ConcurrentHashMap 的設計比 HashMap 復雜得多。Java 7 時,它使用了 Segment 作為分段鎖的機制。Java 8 之后,Segment 被淘汰,改用了一種基于 CAS(Compare-And-Swap)操作和 Synchronized 鎖的設計。
簡單來說,ConcurrentHashMap 的核心在于 分段和細粒度鎖,它的每個桶(bucket)可以獨立加鎖,從而提高并發(fā)性能。
第二回合:線程安全的實現(xiàn)
1. HashMap:線程不安全
HashMap 沒有任何鎖機制,完全是無鎖設計。在多線程情況下,最典型的問題是死循環(huán),例如兩個線程同時觸發(fā)擴容操作,導致循環(huán)鏈表形成,程序直接掛掉。
2. ConcurrentHashMap:線程安全
ConcurrentHashMap 使用了鎖分段技術來實現(xiàn)線程安全:
- 讀操作:在大多數(shù)情況下是無鎖的,因為它使用了 volatile 修飾來保證可見性。
- 寫操作:通過 CAS 操作和 Synchronized 來保證線程安全。
另外,它還有一個巧妙的設計:分段鎖(Segmented Lock)。每個桶對應一個鎖,多個線程可以同時操作不同的桶,避免了全表加鎖的性能損耗。
第三回合:性能的對比
HashMap 的性能很高,因為它根本沒有鎖,單線程環(huán)境下表現(xiàn)優(yōu)秀。但在多線程環(huán)境中會產生數(shù)據不一致的問題。
ConcurrentHashMap 引入了鎖機制,多線程安全性大大提高,但性能上會稍遜于 HashMap。不過,Java 8 的優(yōu)化讓它在高并發(fā)環(huán)境中表現(xiàn)得非常高效。
第四回合:API 的使用差異
其實從使用層面來看,HashMap 和 ConcurrentHashMap 的 API 非常相似。
HashMap 的常用方法:
- put(K key, V value)
- get(Object key)
- remove(Object key)
ConcurrentHashMap 的特有方法:
- putIfAbsent(K key, V value):只有當 key 不存在時才插入。
- compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):對 key 對應的值進行重新計算。
- merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction):如果 key 已存在,就用新的 value 與舊值進行合并。
這些增強版方法尤其適合在并發(fā)場景中使用。
面試總結:如果再遇到這個問題,我會怎么回答?
“HashMap 和 ConcurrentHashMap 的區(qū)別可以從以下幾個方面來看:”
1、線程安全性:
- HashMap 是線程不安全的,在多線程環(huán)境中不能直接使用。
- ConcurrentHashMap 是線程安全的,通過分段鎖和 CAS 實現(xiàn)高效并發(fā)。
2、底層結構:
- HashMap 使用數(shù)組+鏈表/紅黑樹。
- ConcurrentHashMap 使用分段結構,每個桶獨立加鎖。
3、并發(fā)性能:
- HashMap 在單線程環(huán)境中性能最佳。
- ConcurrentHashMap 在多線程環(huán)境中表現(xiàn)優(yōu)異,尤其適合高并發(fā)場景。
4、API 支持:
- ConcurrentHashMap 增強了線程安全的 API,如 putIfAbsent、compute 和 merge。
結尾感悟:面試翻車也是成長的機會
雖然那次面試沒通過,但這個問題讓我有了更深的認識,也提醒我:技術上的基礎知識不能掉以輕心。