2016-07-14 57 views
2

我在多線程環境中的編碼,我看到線程停留在HashMap.putHashMap的停留在把

34 Threads 
java.util.HashMap.put(HashMap.java:374) 
com.aaa.bbb.MyClass.getDefinitionMap(). 

調查那是HashMap我看到的方法是同步的方法:

@Override 
public synchronized Map<String,String> getDefinitionMap() { 
     //truncated some code here... 
     colDefMap = new HashMap<String,String>();   
     for (CD cd : (List<CD>)cm.getDef()) { 
      colDefMap.put(cd.getIdentifier(),cd); 
     } 
    return colDefMap; 
} 

因此,切換到ConcurrentHashMap後,從方法簽名中刪除synchronized關鍵字並重新啓動應用程序服務器 - 問題已解決。

我的問題是爲什麼在這種情況下同步方法不足以保護地圖免受併發訪問?

+3

很難說沒有[mcve]。你的equals/hashCode方法是否調用其他同步的東西,可能導致死鎖? –

+4

什麼是同步訪問?你在方法中創建'HashMap',所以這個方法不會干擾另一個線程的映射。你只需要同步訪問共享狀態,所以也許你需要同步訪問'cm'? – Samuel

+1

最有可能導致某處出現死鎖的原因。您必須檢查所有可能同步呼叫的方法。沒有足夠的代碼可以肯定地知道。但是如果在同類中還有其他方法,我會很好奇。 'cm'怎麼樣?有沒有鎖定。 。 –

回答

2

你不說這是多麼「卡住」,不管你是否真的有死鎖或瓶頸。

我期望發佈的代碼是一個瓶頸,幾乎所有的線程都試圖訪問同一個對象,等待獲取synchronized方法使用的鎖。很可能無論cm.getDef確實需要一段時間,一次只有一個線程可以取得進展。所以同步確實會保護數據免受併發訪問的影響,只是以吞吐量爲代價。

這符合在the Java concurrency tutorial給「飢餓」的定義:

飢餓描述的情況,其中一個線程是無法獲得對共享資源的訪問規則,無法取得進展。當共享資源由「貪婪」線程長時間不可用時,會發生這種情況。例如,假設一個對象提供了一個經常需要很長時間才能返回的同步方法。如果一個線程頻繁地調用這個方法,那麼其他線程也需要經常同步訪問同一個對象。

正如您所觀察的,切換到ConcurrentHashMap是一個很好的改進。ConcurrentHashMap的避免了鎖螺紋出整個地圖,並支持併發更新,請the API doc(我的重點):

的哈希表支持檢索和更新高預期併發的完全併發。該類遵循與Hashtable相同的功能規範,並且包含與Hashtable的每種方法相對應的方法版本。但是,即使所有操作都是線程安全的,但是,檢索操作不需要鎖定,也不支持以阻止所有訪問的方式鎖定整個表。該類與Hashtable在依賴其線程安全性但不同步詳細信息的程序中完全可互操作。

您可能會考慮緩存任何cm.getDef所做的任何操作,因此您不必每次都調用它,但當然,實用性取決於您的要求。

+0

如果出現瓶頸,ConcurrentHashMap將無法解決問題,對嗎? – user648026

+1

@ user648026:它應該解決這個問題,因爲鎖定的次數要少得多。 –

2

您正在同步子類中的getDefinitionMap方法,即明顯是不是唯一可訪問cm的方法(或類)。

在類變量cm迭代器是有可能是罪魁禍首:

for (CD cd : (List<CD>) cm.getDef()) 
{ 
    colDefMap.put(cd.getIdentifier(), cd); 
} 

在上面的代碼,而你迭代它cm變量可能被修改。

您也可以使用下列內容:

synchronized (cm) 
{ 
    for (CD cd : (List<CD>) cm.getDef()) 
    { 
     colDefMap.put(cd.getIdentifier(), cd); 
    } 
} 

然而,這一切都還是留下的cm開放給其他線程修改,如果沒有類似的同步進行修改cm

正如您發現的那樣,使用集合類的線程安全版本要比在多線程環境中實現非線程安全集合的變通方法容易得多。

+1

因此,在您的方法簽名上設置同步不會阻止競爭狀態? – user648026

+1

@ user648026爲了回答這個問題,你需要考慮你可能與之競爭的對手。將方法設置爲'synchronized'只會阻止同時調用該方法。但是,其他一些方法可能會獨立修改該變量,並且在兩種方法上設置'synchronized'都不能解決該問題。想象一下有兩扇門(前後)的房子,並且你在每扇門上放置了一個門衛,指示一次只允許一個人在房子裏。門童不會互相交談,你最終會在房子裏有兩個人。 – vallismortis

2

我想你可能錯了,認爲你解決了你的問題。刪除同步意味着您解鎖訪問此方法可以解決您的問題,並帶來其他問題。我的意思是你的HashMap是在你的函數範圍內創建的,所以顯然不是你應該有一個併發問題(如果內部放置的不是靜態的或線程安全的)。從來沒有這麼少使用concurrentHashMap沒有效果。

我建議你嘗試在多線程測試中看看,如果你的函數沒有synchronized語句(沒有concurrentMap)沒有正確的工作。

在我看來,如果不知道代碼的其餘部分,此函數可能會訪問靜態或共享數據,這些數據可能會被線程鎖定,所以問題不是來自函數,而是其他對象在某個點上與其交互。