1. 程式人生 > >多執行緒環境下操作HashMap的問題

多執行緒環境下操作HashMap的問題

HashMap為什麼不是執行緒安全,併發操作Hashmap會帶來什麼問題:
這個問題曾經有一個面試官問過我,當時我天真的以為是讀寫操作併發時存在髒資料的問題,當時面試官不置可否。我後面回來查資料,發現沒有那麼簡單。併發操作HashMap,是有可能帶來死迴圈以及資料丟失的問題的。

情景如下程式碼:

    public class HashMapInfiniteLoop {    
       
        private static HashMap<Integer,String> map = new HashMap<Integer,String>(2,0.75f);    
        public static void main(String[] args) {    
            map.put(5, "C");    
       
            new Thread("Thread1") {    
                public void run() {    
                    map.put(7, "B");    
                    System.out.println(map);    
                };    
            }.start();    
            new Thread("Thread2") {    
                public void run() {    
                    map.put(3, "A);    
                    System.out.println(map);    
                };    
            }.start();          
        }    
    }  


其中,map初始化為一個長度為2的陣列,loadFactor=0.75,threshold=2*0.75=1,也就是說當put第二個key的時候,map就需要進行擴容。

考慮這樣一種情況:
先放出transfer的部分程式碼:

    do {  
        Entry<K,V> next = e.next; //假設執行緒一執行到這裡就被排程掛起了  
        int i = indexFor(e.hash, newCapacity);  
        e.next = newTable[i];  
        newTable[i] = e;  
        e = next;  
    } while (e != null);  

執行緒1、執行緒2都添加了資料之後,執行緒1執行到transfer()方法的第一行就被排程掛起了,這時執行緒2被排程來執行擴容操作。執行緒2的擴容操作結束之後,執行緒1被排程回來繼續執行,此時由於執行緒2的執行,e已經指向了執行緒2修改之後的反轉連結串列,但是執行緒1並不知道執行緒2已經在它之前做過這些操作了,於是它繼續往下走,此時next=key(7),

然後計算索引。索引計算完之後執行e.next=newTable[i],此時e.next=key(7)。繼續往下走,newTable[i]=e,此時newTable[i]=key(3),再往下,e=next,此時e指向了key(7),本次迴圈結束。從執行緒二重組連結串列結束,到執行緒1第一輪迴圈結束的變化圖如下:


一切看起來都還沒有什麼問題。然後新一輪迴圈開始

這一輪迴圈我們不需要走完,就能發現問題。

第一句,執行後為:next=null;

第二句,計算索引,還是i

第三句,在這裡就出問題了,這句話執行的是e.next=newTable[i],我們看上圖,newTable[i]指向的是key(3),因此出現連結串列末尾的元素的next指標指向了連結串列頭,迴圈連結串列就出現了。(按道理,HashMap是不存在迴圈連結串列的。)

第四句話,將連結串列頭的元素換成key(7),而迴圈連結串列依然存在。

第五句,e=null,執行到這迴圈結束,因為e=null了。

整個過程並不會發生明顯的異常。看起來一切安好。順利的完成了rehash,但是悲劇在後面:當我們呼叫get()這個連結串列中不存在的元素的時候,就會出現死迴圈。go die

一句話總結就是,併發環境下的rehash過程可能會帶來迴圈連結串列,導致死迴圈致使執行緒掛掉。

因此併發環境下,建議使用Java.util.concurrent包中的ConcurrentHashMap以保證執行緒安全。

至於HashTable,它並未使用分段鎖,而是鎖住整個陣列,高併發環境下效率非常的低,會導致大量執行緒等待。
同樣的,Synchronized關鍵字、Lock效能都不如分段鎖實現的ConcurrentHashMap。

相關推薦

執行環境操作HashMap的問題

HashMap為什麼不是執行緒安全,併發操作Hashmap會帶來什麼問題: 這個問題曾經有一個面試官問過我,當時我天真的以為是讀寫操作併發時存在髒資料的問題,當時面試官不置可否。我後面回來查資料,發現

你是否聽說過 HashMap執行環境操作可能會導致程式死迴圈?

作者:炸雞可樂 原文出處:www.pzblog.cn 一、問題描述 經常有些面試官會問,是否瞭解過 HashMap 在多執行緒環境下使用時可能會發生死迴圈,導致伺服器 cpu 100% 的線上故障? 關於這個問題,很多年前,在淘寶內網裡就有很多的程式設計師發過這種帖子說一個CPU 被100%了,原因竟是多

HashMap執行環境的死迴圈問題解釋

hashMap在多執行緒環境下,呼叫put方法出現的死迴圈是由於擴容時候resize方法導致的連結串列出現迴圈。 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity =

java執行環境對變數的讀寫操作的原子性問題

本文轉載自:http://www.cnblogs.com/qlee/archive/2011/09/13/2174434.html 以下多執行緒對int型變數x的操作,哪幾個需要進行同步:( )A. x=y; B. x++; C. ++x; D. x=1;從表面看上去實在

Java執行環境的懶漢模式解決方案

一、場景簡述 單例模式下有餓漢模式和懶漢模式,其中懶漢模式在於呼叫相關方法時例項才被建立。懶漢模式我們不難實現,但是在懶漢模式下我們如果使用多執行緒,就會取出多個例項的情況,與單例模式相違背,所以該篇部落格筆者主要關於在多執行緒環境下利用DCL雙檢查鎖機制來實現懶漢模式。

linux執行環境的搶屍行為(system返回-1:No child processes)

#!/usr/bin/env python #coding:utf8 import os import time pid = os.fork() if pid: print 'in parent.sleepin....' while True:

執行環境呼叫 HttpWebRequest 併發連線限制

.net 的 HttpWebRequest 或者 WebClient 在多執行緒情況下存在併發連線限制,這個限制在桌面作業系統如 windows xp , windows  7 下預設是2,在伺服器作業系統上預設為10. 如果不修改這個併發連線限制,那麼客戶端同時可以建立的

執行環境的ConcurrentHashMap

文章目錄 什麼是ConcurrentHashMap? 底層資料結構? 如何實現併發安全? transfer和ForwardingNode Hash計算 什麼是ConcurrentHashMap? C

執行環境使用log4j輸出各執行的標識,區分各執行輸出的內容

在多執行緒環境下,我們可能需要輸出很多資訊,每個執行緒產生的日誌資訊可能都是類似的,我們如何區分出哪些資訊是同一個執行緒輸出的呢?其實log4j已經提供了多種實現方式: 1.使用PatternLayout,在設定輸出格式的時候增加%t引數,這樣會輸出各個執行緒的執行緒名稱,這樣我們就可以根據執行緒名稱區分哪

從專案中談JAVA中static 方法在執行環境的運用

最近在做一個實時性比較高的系統,資料庫訪問很頻繁。在這個專案中資料庫訪問的大多數都被我做成了static 方法。(這樣做應該不是很好,沒辦法發揮dao模式的優勢,不過湊合用吧)因這個系統是多執行緒。static方法的運用需要注意,不能在servlet中使用成員變數,因為如果你

(轉載)執行環境的Map一定要同步嗎?

原文地址:http://pt.alibaba-inc.com/wp/experience_644/map-multi-threaded-environment-you-must-be-synchronized.html 我們都知道在多執行緒操縱Map時,需要對Map資料結構

執行環境不安全的訊息佇列存取---執行不同步會造成隱患

        前面, 我們把訊息佇列存取都放在主執行緒中, 而在實際應用中, 很多時候, 存訊息佇列在主執行緒, 取訊息佇列在其他執行緒(如網路執行緒)。 下面, 我們將之前的程式改為多執行緒程式: #include <windows.h> #include

Android開發之執行環境更新介面

Android應用程式的介面運行於獨立的執行緒裡。但有時候軟體需要單獨的執行緒來處理資料,然後再更新介面。這樣能夠保證介面執行的流暢又不至於影響使用者體驗。這裡的問題在於,UI只能被介面執行緒更新,在多執行緒環境下回出錯。本文會展示這種典型的錯誤,以及解決方案。 下面以計時

C# Winform專案中執行環境, 如何跨執行對Window窗體控制元件進行安全訪問?

請嘗試執行這段程式碼, 結果你會發現微軟開發工具會提示, Tb_Text.Text = int_Index.ToString(); 涉及"對Windows窗體控制元件進行執行緒安全呼叫", 並給瞭如下的解決方案:https://msdn.microsoft.com/zh-cn/library/ms171728

mktime和localtime_r能在執行環境使用麼?

localtime和mktime是用來在時間分量和時間秒數之間進行轉換的標準c函式。 在glibc的文件描述中,localtime的實現是使用了一個內部靜態快取來儲存結果,所以這是一個不可用於多執行緒環境的api。glibc提供了一個執行緒安全版本localtime_r。mk

Spring執行環境Bean的管理策略

一、Bean的狀態:stateful 與 stateless 有狀態會話bean:每個使用者有自己特有的一個例項,在使用者的生存期內,bean保持了使用者的資訊,即“有狀態”; 一旦使用者滅亡(呼叫

localtime_r在執行環境可能存在死鎖

         但是在某些情況下,localtime_r可能存在死鎖的情況,使用如下的測試程式:        #include <pthread.h> #include <time.h>        void *mytest(void *arg) {         pthrea

執行環境安全的訊息佇列存取---利用訊號量

       前面幾篇博文中, 我們一直在討論異質連結串列訊息佇列問題, 在本文中, 我們繼續來探討這一問題, 在多執行緒環境下, 考慮執行緒同步問題, 程式碼如下(我用Sleep(20);來故意搗亂): #include <windows.h> #inclu

執行環境的計數器

下面的例子是通過CAS來實現一個多執行緒環境下的安全計數器 package cn.com.test.vol.p201612; import java.util.concurrent.Executo

執行環境使用openssl

static cyg_mutex_t *lock_cs;// 多執行緒保護的初始化 // This function allocates and initializes the lock array // and registers the callbacks. This should be called /