簡體   English   中英

並發修改例外

[英]Concurrent Modification Exception

我目前正在研究一個多線程應用程序,偶爾也會收到一個同時修改的異常(平均大約每小時一次或兩次,但是看似隨機的間隔)。

有缺陷的類本質上是地圖的包裝器 - 它擴展了LinkedHashMap (將accessOrder設置為true)。 該類有幾個方法:

synchronized set(SomeKey key, SomeValue val)

set方法將鍵/值對添加到內部映射,並受synchronized關鍵字的保護。

synchronized get(SomeKey key)

get方法根據輸入鍵返回值。

rebuild()

內部地圖偶爾重建一次(〜每2分鍾,間隔與異常不匹配)。 rebuild方法基本上根據鍵重建值。 由於rebuild()相當昂貴,我沒有在方法上放置synchronized關鍵字。 相反,我正在做:

public void rebuild(){
  /* initialization stuff */
  List<SomeKey> keysCopy = new ArrayList<SomeKey>();
  synchronized (this) {
    keysCopy.addAll(internalMap.keySet());
  }
  /* 
    do stuff with keysCopy, update a temporary map
   */    
  synchronized (this) {
    internalMap.putAll(tempMap);
  }
}

例外情況發生在

keysCopy.addAll(internalMap.keySet());

在synchronized塊內。

建議非常感謝。 請隨意向我指出Effective Java和/或Concurrency in Practice中的特定頁面/章節。

更新1:

消毒堆棧跟蹤:

java.util.ConcurrentModificationException
        at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:365)
        at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:376)
        at java.util.AbstractCollection.toArray(AbstractCollection.java:126)
        at java.util.ArrayList.addAll(ArrayList.java:473)
        at a.b.c.etc.SomeWrapper.rebuild(SomeWraper.java:109)
        at a.b.c.etc.SomeCaller.updateCache(SomeCaller.java:421)
        ...

更新2:

感謝大家到目前為止的答案。 我認為問題在於LinkedHashMap及其accessOrder屬性,盡管我並不完全確定atm(調查)。

如果LinkedHashMap上的accessOrder設置為true,並且我訪問其keySet然后繼續通過addAll將keySet添加到linkedList,這些操作中的任何一個是否會改變順序(即計入“訪問”)?

如果您使用accessOrder = true構造LinkedHashMap,那么LinkedHashMap.get()實際上會改變LinkedHashMap,因為它將最近訪問的條目存儲在鏈接的條目列表的前面。 當數組列表使用Iterator進行復制時,可能會調用get()。

此異常通常與同步無關 - 如果在Iterator迭代時修改了Collection,通常會拋出此異常。 AddAll方法可能使用迭代器 - 值得注意的是,posh foreach循環也會迭代Iterator的實例。

例如:

for(Object o : objects) {
    objects.remove(o);
}

足以在某些集合上獲得異常(例如ArrayList)。

詹姆士

這些都是你的包裝中的功能嗎? 因為當你以某種方式在另一個地方迭代集合時,可能拋出此異常。 而且我猜你的方法與潛在的明顯競爭條件同步,但可能錯過了不太明顯的情況。 這里是對異常類docs的引用。

來自Javadoc:

如果多個線程同時訪問鏈接的哈希映射,並且至少有一個線程在結構上修改了映射,則必須在外部進行同步。 這通常通過在自然封裝地圖的某個對象上進行同步來實現。 如果不存在此類對象,則應使用Collections.synchronizedMap方法“包裝”該映射。 這最好在創建時完成,以防止意外地不同步訪問地圖:

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

實際包裝LinkedHashMap而不是聲稱擴展它可能更安全。 所以你的實現將有一個內部數據成員,它是Collections.synchronizedMap返回的Map(new LinkedHashMap(...))。

有關詳細信息,請參閱Collections javadoc: Collections.synchronizedMap

嘗試從set()和get()方法中刪除synchronized關鍵字,而是使用方法內部的synchronized塊,鎖定internalMap; 然后更改rebuild()方法上的synchronized塊以鎖定internalMap。

這很奇怪。 我無法看到在您識別的位置拋出異常的任何方式(在Java 6的默認實現中)。 你在ArrayList或HashMap中得到ConcurrentModificationException? 你重寫了HashMapkeySet()方法嗎?

編輯我的錯誤 - ArrayList將強制KeySet迭代(AbstractCollection.toArray()將迭代其鍵)

代碼中的某個位置允許您更新未同步的內部映射。

  • 你有一個公用事業方法暴露你的內部地圖“不應該使用”或
  • 是您的內部地圖被確定為公共范圍

internalMap是靜態的嗎? 您可能有多個對象,每個對象都鎖定this對象,但不能在靜態internalMap上提供正確的鎖定。

我不認為你的set() / get()rebuild()所在的同一個監視器是同步的。 這使得有人可以在執行有問題的行時調用set / get,特別是在addAll()調用期間迭代addAll()鍵集時(通過堆棧跟蹤公開的內部實現)。

你沒有嘗試使set()同步,而是嘗試了以下方面:

public void set(SomeKey key, SomeValue val) {
    synchronized(this) {
        internalMap.put(key, val); // or whatever your get looks like
    }
}

我認為你根本不需要同步get() ,但如果你堅持:

public SomeValue get(SomeKey key) {
    synchronized(this) {
        internalMap.get(key); // or whatever your get looks like
    }
}

事實上,我認為你最好對同步internalMap ,而不是this 它也不會傷害到讓internalMap volatile ,但我不認為這是如果你確定真的必要的set() / get() / rebuild()是唯一的方法直接訪問internalMap,而且都是以同步方式訪問它。

private volatile Map internalMap ...

在迭代鍵時,由於

keysCopy.addAll(internalMap.keySet());

您是否認為某些條目可以從LinkedHashMap中刪除?

removeEldestEntry方法可以返回true,也可以修改映射,從而擾亂迭代。

嘗試將Map聲明為瞬態

試試這個:

public void rebuild(){
  /* initialization stuff */
  List<SomeKey> keysCopy = new ArrayList<SomeKey>();
  synchronized (internalMap) {
    keysCopy.addAll(internalMap.keySet());
  }
  /* 
    do stuff with keysCopy, update a temporary map
   */    
  synchronized (internalMap) {
    internalMap.putAll(tempMap);
  }
}

保持對internalMap 同步的訪問,否則會發生java.util.ConcurrentModificationException,因為在鍵集迭代期間可以同時更改HashMap#modCount(記錄結構更改)(由於keysCopy.addAll(internalMap.keySet()調用)。

LinkedHashMap javaDoc指定:“在訪問順序鏈接哈希映射中,僅使用get查詢映射是一種結構修改。”

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM