當(dāng)前位置:
首頁(yè) >
非线程安全的HashMap 和 线程安全的ConcurrentHashMap
發(fā)布時(shí)間:2025/6/17
40
豆豆
生活随笔
收集整理的這篇文章主要介紹了
非线程安全的HashMap 和 线程安全的ConcurrentHashMap
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
在平時(shí)開(kāi)發(fā)中,我們經(jīng)常采用HashMap來(lái)作為本地緩存的一種實(shí)現(xiàn)方式,將一些如系統(tǒng)變量等數(shù)據(jù)量比較少的參數(shù)保存在HashMap中,并將其作為單例類(lèi)的一個(gè)屬性。在系統(tǒng)運(yùn)行中,使用到這些緩存數(shù)據(jù),都可以直接從該單例中獲取該屬性集合。但是,最近發(fā)現(xiàn),HashMap并不是線程安全的,如果你的單例類(lèi)沒(méi)有做代碼同步或?qū)ο箧i的控制,就可能出現(xiàn)異常。
首先看下在多線程的訪問(wèn)下,非現(xiàn)場(chǎng)安全的HashMap的表現(xiàn)如何,在網(wǎng)上看了一些資料,自己也做了一下測(cè)試:
?2????
?3????public?static?final?HashMap<String,?String>?firstHashMap=new?HashMap<String,?String>();
?4????
?5????public?static?void?main(String[]?args)?throws?InterruptedException?{
?6????????
?7????????//線程一
?8????????Thread?t1=new?Thread(){
?9????????????public?void?run()?{
10????????????????for(int?i=0;i<25;i++){
11????????????????????firstHashMap.put(String.valueOf(i),?String.valueOf(i));
12????????????????}
13????????????}
14????????};
15????????
16????????//線程二
17????????Thread?t2=new?Thread(){
18????????????public?void?run()?{
19????????????????for(int?j=25;j<50;j++){
20????????????????????firstHashMap.put(String.valueOf(j),?String.valueOf(j));
21????????????????}
22????????????}
23????????};
24????????
25????????t1.start();
26????????t2.start();
27????????
28????????//主線程休眠1秒鐘,以便t1和t2兩個(gè)線程將firstHashMap填裝完畢。
29????????Thread.currentThread().sleep(1000);
30????????
31????????for(int?l=0;l<50;l++){
32????????????//如果key和value不同,說(shuō)明在兩個(gè)線程put的過(guò)程中出現(xiàn)異常。
33????????????if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){
34????????????????System.err.println(String.valueOf(l)+":"+firstHashMap.get(String.valueOf(l)));
35????????????}
36????????}
37????????
38????}
39
40}
上面的代碼在多次執(zhí)行后,發(fā)現(xiàn)表現(xiàn)很不穩(wěn)定,有時(shí)沒(méi)有異常文案打出,有時(shí)則有個(gè)異常出現(xiàn):
為什么會(huì)出現(xiàn)這種情況,主要看下HashMap的實(shí)現(xiàn):
1public?V?put(K?key,?V?value)?{
?2????if?(key?==?null)
?3????????return?putForNullKey(value);
?4????????int?hash?=?hash(key.hashCode());
?5????????int?i?=?indexFor(hash,?table.length);
?6????????for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{
?7????????????Object?k;
?8????????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{
?9????????????????V?oldValue?=?e.value;
10????????????????e.value?=?value;
11????????????????e.recordAccess(this);
12????????????????return?oldValue;
13????????????}
14????????}
15
16????????modCount++;
17????????addEntry(hash,?key,?value,?i);
18????????return?null;
19????}
我覺(jué)得問(wèn)題主要出現(xiàn)在方法addEntry,繼續(xù)看:
1void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{
2????Entry<K,V>?e?=?table[bucketIndex];
3????????table[bucketIndex]?=?new?Entry<K,V>(hash,?key,?value,?e);
4????????if?(size++?>=?threshold)
5????????????resize(2?*?table.length);
6????}
從代碼中,可以看到,如果發(fā)現(xiàn)哈希表的大小超過(guò)閥值threshold,就會(huì)調(diào)用resize方法,擴(kuò)大容量為原來(lái)的兩倍,而擴(kuò)大容量的做法是新建一個(gè)Entry[]:
1void?resize(int?newCapacity)?{
?2????????Entry[]?oldTable?=?table;
?3????????int?oldCapacity?=?oldTable.length;
?4????????if?(oldCapacity?==?MAXIMUM_CAPACITY)?{
?5????????????threshold?=?Integer.MAX_VALUE;
?6????????????return;
?7????????}
?8
?9????????Entry[]?newTable?=?new?Entry[newCapacity];
10????????transfer(newTable);
11????????table?=?newTable;
12????????threshold?=?(int)(newCapacity?*?loadFactor);
13????}
一般我們聲明HashMap時(shí),使用的都是默認(rèn)的構(gòu)造方法:HashMap<K,V>,看了代碼你會(huì)發(fā)現(xiàn),它還有其它的構(gòu)造方法:HashMap(intinitialCapacity, float loadFactor),其中參數(shù)initialCapacity為初始容量,loadFactor為加載因子,而之前我們看到的threshold = (int)(capacity* loadFactor);如果在默認(rèn)情況下,一個(gè)HashMap的容量為16,加載因子為0.75,那么閥值就是12,所以在往HashMap中put的值到達(dá)12時(shí),它將自動(dòng)擴(kuò)容兩倍,如果兩個(gè)線程同時(shí)遇到HashMap的大小達(dá)到12的倍數(shù)時(shí),就很有可能會(huì)出現(xiàn)在將oldTable轉(zhuǎn)移到newTable的過(guò)程中遇到問(wèn)題,從而導(dǎo)致最終的HashMap的值存儲(chǔ)異常。
JDK1.0引入了第一個(gè)關(guān)聯(lián)的集合類(lèi)HashTable,它是線程安全的。 HashTable的所有方法都是同步的。
JDK2.0引入了HashMap,它提供了一個(gè)不同步的基類(lèi)和一個(gè)同步的包裝器synchronizedMap。synchronizedMap被稱為有條件的線程安全類(lèi)。
JDK5.0util.concurrent包中引入對(duì)Map線程安全的實(shí)現(xiàn)ConcurrentHashMap,比起synchronizedMap,它提供了更高的靈活性。同時(shí)進(jìn)行的讀和寫(xiě)操作都可以并發(fā)地執(zhí)行。
所以在開(kāi)始的測(cè)試中,如果我們采用ConcurrentHashMap,它的表現(xiàn)就很穩(wěn)定,所以以后如果使用Map實(shí)現(xiàn)本地緩存,為了提高并發(fā)時(shí)的穩(wěn)定性,還是建議使用ConcurrentHashMap。
====================================================================
另外,還有一個(gè)我們經(jīng)常使用的ArrayList也是非線程安全的,網(wǎng)上看到的有一個(gè)解釋是這樣:
一個(gè) ArrayList 類(lèi),在添加一個(gè)元素的時(shí)候,它可能會(huì)有兩步來(lái)完成:1. 在 Items[Size] 的位置存放此元素;2.增大 Size 的值。
在單線程運(yùn)行的情況下,如果 Size = 0,添加一個(gè)元素后,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個(gè)線程,線程 A 先將元素存放在位置 0。但是此時(shí) CPU 調(diào)度線程A暫停,線程 B得到運(yùn)行的機(jī)會(huì)。線程B也將元素放在位置0,(因?yàn)閟ize還未增長(zhǎng)),完了之后,兩個(gè)線程都是size++,結(jié)果size變成2,而只有items[0]有元素。
util.concurrent包也提供了一個(gè)線程安全的ArrayList替代者CopyOnWriteArrayList。
本文轉(zhuǎn)載自:http://www.blogjava.net/lukangping/articles/331089.html
轉(zhuǎn)載于:https://www.cnblogs.com/yangkai-cn/p/4016573.html
總結(jié)
以上是生活随笔為你收集整理的非线程安全的HashMap 和 线程安全的ConcurrentHashMap的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [转载]明天的数字营销分析工具2
- 下一篇: [实验]通过内核Patch去掉iOS-v