![](/img/trans.png)
[英]Difference between Collections.synchronizedMap() and synchronized
[英]Will Collections.synchronizedMap be thread safe if synchronized while iteration?
我正在嘗試在迭代Collections.synchronizedMap
時修復與ConcurrentModificationException
相關的錯誤。
根據Javadoc的要求,迭代過程已在地圖上同步。
我檢查了迭代過程,對地圖的大小沒有明顯的修改(調用trace的方法很長,我會仔細檢查)。
除了在迭代過程中進行修改外,是否還有其他可能導致此異常的可能性?
由於迭代已經同步,據我所知,其他線程將無法進行類似add()
或remove()
,對嗎?
我真的是這些東西的新手。 任何幫助將不勝感激。
更新
非常感謝大家的幫助,尤其是@ Marco13的詳細說明。 我做了一個小的代碼來測試和驗證這個問題,代碼附在這里:
public class TestCME {
public static void main(String[] args){
TestMap tm = new TestMap();
for(int i = 0;i < 50;i++){
tm.addCity(i,new City(i * 10));
}
RunnableA rA = new RunnableA(tm);
new Thread(rA).start();
RunnableB rB = new RunnableB(tm);
new Thread(rB).start();
}
}
class TestMap{
Map<Integer,City> cityMap;
public TestMap(){
cityMap = Collections.synchronizedMap(new HashMap<Integer,City>());
}
public Set<Integer> getAllKeys(){
return cityMap.keySet();
}
public City getCity(int id){
return cityMap.get(id);
}
public void addCity(int id,City city){
cityMap.put(id,city);
}
public void removeCity(int id){
cityMap.remove(id);
}
}
class City{
int area;
public City(int area){
this.area = area;
}
}
class RunnableA implements Runnable{
TestMap tm;
public RunnableA(TestMap tm){
this.tm = tm;
}
public void run(){
System.out.println("Thread A is starting to run......");
if(tm != null && tm.cityMap != null && tm.cityMap.size() > 0){
synchronized (tm.cityMap){
Set<Integer> idSet = tm.getAllKeys();
Iterator<Integer> itr = idSet.iterator();
while(itr.hasNext()){
System.out.println("Entering while loop.....");
Integer id = itr.next();
System.out.println(tm.getCity(id).area);
try{
Thread.sleep(100);
}catch(Exception e){
e.printStackTrace();
}
}
}
/*Set<Integer> idSet = tm.getAllKeys();
Iterator<Integer> itr = idSet.iterator();
while(itr.hasNext()){
System.out.println("Entering while loop.....");
Integer id = itr.next();
System.out.println(tm.getCity(id).area);
try{
Thread.sleep(100);
}catch(Exception e){
e.printStackTrace();
}
}*/
}
}
}
class RunnableB implements Runnable{
TestMap tm;
public RunnableB(TestMap tm){
this.tm = tm;
}
public void run(){
System.out.println("Thread B is starting to run......");
System.out.println("Trying to add elements to map....");
tm.addCity(50,new City(500));
System.out.println("Trying to remove elements from map....");
tm.removeCity(1);
}
}
我試圖恢復我的錯誤,因此代碼有點冗長,對此我感到抱歉。 在線程A中,我正在對地圖進行迭代,而在線程B中,我試圖在地圖中添加和刪除元素。 在地圖上進行正確的同步(如@ Marco13建議),如果沒有同步或在TestMap對象上進行同步,則不會顯示ConcurrentModificationException。 我想我現在已經了解了這個問題。 非常歡迎對此提出任何雙重確認或建議。 再次非常感謝。
基於已添加(出於任何原因同時又將其刪除)的代碼的一些常規語句。
編輯另請參閱答案底部的更新
你應該知道什么 ,你實際上是同步的。 當您使用創建地圖時
Map<Key, Value> synchronizedMap =
Collections.synchronizedMap(map);
那么同步將采取對地方synchronizedMap
。 這意味着以下是線程安全的:
void executedInFirstThread()
{
synchronizedMap.put(someKey, someValue);
}
void executedInSecondThread()
{
synchronizedMap.remove(someOtherKey);
}
盡管兩個方法可以由不同的線程同時執行,但是它是線程安全的:當第一個線程執行put
方法時,第二個線程將必須等待執行remove
方法。 put
和remove
方法將永遠不會同時執行。 這就是同步的目的。
但是, ConcurrentModificationException
的含義更廣泛。 關於特定的情況下,和一個位被簡化了:該ConcurrentModificationException
表明, 盡管在地圖上的迭代是正在進行的地圖進行了修改。
因此,必須同步整個迭代 ,而不僅僅是單個方法:
void executedInFirstThread()
{
synchronized (synchronizedMap)
{
for (Key key : synchronizedMap.keySet())
{
System.out.println(synchronizedMap.get(key));
}
}
}
void executedInSecondThread()
{
synchronizedMap.put(someKey, someValue);
}
如果沒有synchronized
塊,則第一個線程可以部分地迭代映射,然后在迭代過程中,第二個線程可以調用put
在映射上的命令(因此,執行並發修改 )。 然后,當第一個線程想要繼續進行迭代時,將拋出ConcurrentModificationException
。
對於synchronized
塊,這不會發生:當第一個線程進入此synchronized
塊時,第二個線程將必須等待,然后才能調用put
。 (它必須等到第一個線程離開synchronized
塊。之后,它才能安全地進行修改)。
但是,請注意,整個概念始終指的是在上同步的對象 。
就您而言,您有一個類似於以下內容的類:
class TestMap
{
Map<Key, Value> synchronizedMap =
Collections.synchronizedMap(new HashMap<Key, Value>());
public Set<Key> getAllKeys()
{
return synchronizedMap.keySet();
}
}
然后,您像這樣使用它:
TestMap testMap = new TestMap();
synchronized(testMap)
{
Set<Key> keys = testMap.getAllKeys();
for (Key key : keys)
{
...
}
}
}
在這種情況下,你是同步的testMap
對象,而不是在sychronizedMap
它所包含的對象。 因此,迭代沒有同步發生。 一個線程可以執行迭代(在testMap
對象上同步)。 同時,其他線程可以(同時)修改synchronizedMap
,這將導致ConcurrentModificationException
。
解決方案取決於整體結構。 一種可能的解決方案是公開synchronizedMap
...
class TestMap
{
Map<Key, Value> synchronizedMap =
Collections.synchronizedMap(new HashMap<Key, Value>());
...
public Object getSynchronizationMonitor()
{
return synchronizedMap;
}
}
並將其用於迭代的同步...
TestMap testMap = new TestMap();
synchronized(testMap.getSynchronizationMonitor())
{
...
}
...但這不是一般性建議 ,而只是為了傳達這個想法 :您必須知道要在哪個對象上進行同步。 對於實際應用,最有可能是更優雅的解決方案。
更新:
再次說明,現在已將“真實”代碼再次添加到問題中:您還可以考慮使用ConcurrentHashMap
。 它明確設計為可同時使用,並在內部進行所有必要的同步-特別是,它在內部使用一些技巧來避免“大”同步塊。 在問題中當前顯示的代碼中,映射將在迭代時被“鎖定”,並且其他線程將無法對映射進行任何修改,這可能會對整體性能產生負面影響。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.