ConcurrentHashMap上的竞争条件问题

时间:2015-03-03 10:30:19

标签: java multithreading concurrency thread-safety race-condition

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

我甚至尝试使用volatile布尔值创建自己的锁机制,但它不会起作用。将 Google Guava Lists.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 个答案:

答案 0 :(得分: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);
      ...
}

不是原子的。或者您可以使用synchronized部分或任何其他标准锁定机制(例如,ReadWriteLock)

此外,如果您使用一个锁处理读写列表,则不必使用同步列表。此外,您甚至不需要ConcurrentHashMap。

所以:

  1. 使用一个全局锁和普通的HashMap / ArrayList OR
  2. 删除全局锁,在列表 OR
  3. 的每个特定实例上使用ConcurrentHashMap和plain ArrayList与 synchronized
  4. 使用队列(某些BlockingQueue或ConcurrentLinkedQueue)代替您当前的所有内容 OR
  5. 使用Disruptor(http://lmax-exchange.github.io/disruptor/)之类的东西与许多选项进行线程间通信。此外,这是一个如何构建无锁队列http://psy-lob-saw.blogspot.ru/2013/03/single-producerconsumer-lock-free-queue.html
  6. 的好例子

答案 1 :(得分:0)

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

我建议,您不要尝试锁定整个地图操作,但要注意使用线程安全访问列表可能正在使用VectorSynchronizedList

另请注意,两个线程的条目if (!VolatileTest.lock) {意味着它们都可以在最初的同时运行,默认情况下,boolean将保持false值,并且两者可能同时尝试在同一列表上工作。

答案 2 :(得分:0)

如前所述,锁定模式看起来不合适。最好使用synchronized。以下代码适用于我

final Object obj = new Object();

然后

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