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