為什么HashMap是不安全的
HashMap的線程不安全是由于它本身的機(jī)制造成的。HashMap的存儲(chǔ)結(jié)構(gòu)是由數(shù)組+單鏈表組成的。
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
HashMap的內(nèi)存存儲(chǔ)使用了Node,Node中有一個(gè)屬性為next,相當(dāng)于是一個(gè)鏈表,所有hash值相同的key會(huì)存儲(chǔ)到一個(gè)鏈表里。
PS:在Java 8中如果hash值相同的key數(shù)量大于指定值(默認(rèn)是8)時(shí)使用平衡樹(shù)來(lái)代替鏈表,這會(huì)將get()方法的性能從O(n)提高到O(logn)。
HashMap的Node數(shù)組默認(rèn)大小為16,默認(rèn)負(fù)載因子為0.75。如果當(dāng)前數(shù)據(jù)長(zhǎng)度大于當(dāng)前最大容量*負(fù)載因子時(shí),就會(huì)執(zhí)行擴(kuò)容resize(),擴(kuò)容大小為之前的2倍。
HashMap在并發(fā)執(zhí)行put操作時(shí)會(huì)引起死循環(huán),導(dǎo)致CPU利用率接近100%。因?yàn)槎嗑€程會(huì)導(dǎo)致HashMap的Node鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu),一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu),Node的next節(jié)點(diǎn)永遠(yuǎn)不為空,就會(huì)在獲取Node時(shí)產(chǎn)生死循環(huán)。
如何使用線程安全的HashMap
Hashtable
Hashtable內(nèi)部使用了synchronized來(lái)保證線程安全,效率較低。
ConcurrentHashMap
ConcurrentHashMap引入segment的概念。Segment在實(shí)現(xiàn)上繼承了ReentrantLock。
Java8中摒棄了segment了,使用CAS的算法。
SynchronizedMap
Collections.getSynchronizedMap()返回一個(gè)SynchronizedMap()對(duì)象,使用synchronized關(guān)鍵字來(lái)保證對(duì)Map的操作是多線程安全的。
效率
ConcurrentHashMap > (SynchronizedMap or HashTable)