1

我有一個多線程的應用程序,其中n個線程寫入ConcurrentHashMap。另有n個線程從該映射中讀取並將其值複製到副本列表中。 之後,原始列表將從地圖中移除。 由於某種原因,我總是得到ConcurrentModificationExceptionConcurrentHashMap競賽條件問題

我甚至試圖用volatile布爾創建我自己的鎖定機制,但它不起作用。當使用Google GuavaLists.newLinkedList()我得到ConcurrentModificationException。當使用StandardWay new LinkedList(list)時,我得到一個ArrayOutOfBoundsException

以下是編譯的代碼示例:

public class VolatileTest { 

public static Map<String, List<String>> logMessages = new ConcurrentHashMap<String, List<String>>(); 

public static AtomicBoolean lock = new AtomicBoolean(false); 

public static void main(String[] args) { 
new Thread() { 

    public void run() { 
    while (true) { 
     try { 
     if (!VolatileTest.lock.get()) { 
      VolatileTest.lock.set(true); 
      List<String> list = VolatileTest.logMessages.get("test"); 
      if (list != null) { 
      List<String> copyList = Collections.synchronizedList(list); 
      for (String string : copyList) { 
       System.out.println(string); 
      } 
      VolatileTest.logMessages.remove("test"); 
      } 
      VolatileTest.lock.set(false); 
     } 
     } catch (ConcurrentModificationException ex) { 
     ex.printStackTrace(); 
     System.exit(1); 
     } 
    } 
    }; 
}.start(); 

new Thread() { 

    @Override 
    public void run() { 
    while (true) { 
     if (!VolatileTest.lock.get()) { 
     VolatileTest.lock.set(true); 
     List<String> list = VolatileTest.logMessages.get("test"); 
     if (list == null) { 
      list = Collections.synchronizedList(new LinkedList<String>()); 
     } 
     list.add("TestError"); 
     VolatileTest.logMessages.put("test", list); 
     VolatileTest.lock.set(false); 
     } 
    } 
    } 
}.start(); 

} 

回答

3

你有ConcurrentModificationException的,因爲你有你的鎖打破,讀者線程讀取作者在同一時間寫入的同一個列表(通過Iterator)。

你的代碼看起來像是一個無鎖編碼的嘗試。如果是這樣,你必須使用CAS操作是這樣的:

while (!VolatileTest.lock.compareAndSet(false, true) { } // or while (VolatileTest.lock.getAndSet(true)) {} - try to get lock 
try { 
    // code to execute under lock 
} finally { 
    VolatileTest.lock.set(false); // unlock 
} 

if (!VolatileTest.lock.get()) { 
     VolatileTest.lock.set(true); 
     ... 
} 

不是原子。或者,您可以使用同步部分或任何其他標準鎖定機制(例如,ReadWriteLock)。

此外,如果處理使用一個鎖讀取和寫入的列表,則不必使用同步列表。而且,甚至不需要ConcurrentHashMap。

所以:

  1. 使用一個全局鎖和普通的HashMap/ArrayList的
  2. 刪除您的全局鎖,在列表中的每個特定實例使用的ConcurrentHashMap和滑動的ArrayList與同步OR
  3. 使用隊列(一些BlockingQueue或ConcurrentLinkedQueue)而不是所有當前的東西
  4. 使用類似Disruptor(http://lmax-exchange.github.io/disruptor/)的線程間通信有很多選項。另外,這裏是一個很好的示例,說明如何構建無鎖隊列http://psy-lob-saw.blogspot.ru/2013/03/single-producerconsumer-lock-free-queue.html
0

ConcurrentHashMap是故障安全意味着你不會遇到ConcurrentModificationException。這是您的List<String>,其中一個線程試圖讀取數據,而另一個線程在迭代時嘗試刪除數據。

我建議,你不要試圖鎖定整個地圖操作,而是要注意讓線程安全地訪問列表可能使用VectorSynchronizedList

另請注意,您的輸入條件if (!VolatileTest.lock) {對於這兩個線程均意味着它們可以同時運行,默認情況下,布爾值將保留false值,並且可能會同時嘗試在同一列表上工作。

+0

爲什麼我有任何問題,因爲我鎖定了完整的寫入/讀取操作? (當前用於測試目的) – 2015-03-03 10:35:59

+1

您的鎖定模式不起作用,您需要對布爾值進行原子「檢查並設置」操作(請參閱AtomicBoolean)。或者一個鎖,它可以達到同樣的目的。 – GPI 2015-03-03 10:39:44

+0

看到我編輯了我的答案。 – SMA 2015-03-03 10:39:47

0

如前所述,鎖定模式看起來不正確。最好使用同步。下面的代碼適用於我

final Object obj = new Object();

然後

同步(OBJ){....}而不是如果(!VolatileTest.lock){} .....

+0

嗨,請看我編輯的代碼,我把volatile更改爲AtomicBoolean。我也將列表更改爲SynchronizedList持有LinkedList,仍然無法正常工作。 – 2015-03-03 10:48:20

+0

切換boolean的實現本身並不能解決你的問題,也就是說,線程檢查你的'lock'布爾值的時間和你設置爲true的時間相同的布爾值(這是你的支票並且不是原子的「意思)。我提到了AtomicBoolean,因爲它在概念上可以解決問題(使用'compareAndSet'方法),但是您必須正確使用它。正如@ramp所建議的那樣,這樣做的可讀性,高效性,簡潔性以及最不容易出錯的方式是使用鎖定和/或同步處理。 – GPI 2015-03-03 11:31:04