ConcurrentHashMap 上的竞争条件问题

Problems with race conditions on ConcurrentHashMap

我有一个多线程应用程序,其中 n 个线程写入 ConcurrentHashMap。另一个 n 线程从该映射中读取并将其值复制到副本列表中。 之后,原始列表将从地图中删除。 出于某种原因,我总是得到 ConcurrentModificationException.

我什至尝试用一个可变布尔值创建我自己的锁机制,但它不起作用。当使用 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();

}

ConcurrentHashMap 是安全的,这意味着您不会遇到 ConcurrentModificationException。这是地图中的 List<String>,其中一个线程试图读取数据,而另一个线程在迭代时试图删除数据。

我建议,您不要尝试锁定整个地图操作,而是注意使对列表的线程安全访问可能正在使用 Vector or SynchronizedList

另请注意,您的两个线程的进入条件 if (!VolatileTest.lock) { 意味着它们可以同时 运行 因为最初默认布尔值将保持 false 值并且两者都可能尝试在同一时间工作同时列出。

如前所述,锁定模式看起来无效。最好使用同步。下面的代码对我有用

最终对象 obj = 新对象();

然后

synchronized (obj){....} 而不是 if (!VolatileTest.lock) {.....}

您有 ConcurrentModificationException,因为您的锁定被破坏,并且 reader 线程读取写入者同时写入的相同列表(通过 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)

另外,如果你用一个锁来处理一个链表的读写,那你就不用用synchronized链表了。而且,你甚至不需要 ConcurrentHashMap。

所以:

  1. 使用一个全局锁和纯 HashMap/ArrayList OR
  2. 删除全局锁,在列表的每个特定实例上使用 ConcurrentHashMap 和带有 synchronized 的普通 ArrayList OR
  3. 使用队列(一些 BlockingQueue 或 ConcurrentLinkedQueue)代替所有当前的东西 OR
  4. 使用像 Disruptor 这样的东西(http://lmax-exchange.github.io/disruptor/) for inter-thread communication with many options. Also, here is a good example of how to build lock-free queues http://psy-lob-saw.blogspot.ru/2013/03/single-producerconsumer-lock-free-queue.html