java - 在迭代中避免不必要的 ConcurrentModificationException

标签 java multithreading concurrency concurrentmodification

我有大量的东西,一个重复迭代它们的线程,以及一个偶尔删除或添加单个东西的单独线程。事物在同步链表中:

private List<Thing> things = Collections.synchronizedList(new LinkedList<Thing>());

我的第一个线程遍历它们:

while (true) {
    for (Thing thing : things) {
        thing.costlyOperation();
    }
    // ...
}

当我的第二个线程添加或删除内容时,上面的迭代器会因 ConcurrentModificationException 而爆炸。但是,在我的上述案例中似乎允许删除和添加。约束是 (A) coSTLyOperation 方法不能在列表中不存在 Thing 时调用,并且 (B) 应该立即调用(在当前或下一次迭代中)事物存在,并且 (C) 通过第二个线程的添加和删除必须快速完成,而不是等待。

在进入迭代循环之前,我可能会在 things 上同步,但这会阻塞另一个线程太久。或者,我可以修改 Thing 以包含一个 isDead 标志,然后执行一个不错的快速 iterator.remove() 循环来摆脱死东西在上面的循环之前,我会再次检查标志。但是我想在这个 for 循环附近解决这个问题,并且污染最小。我还考虑过创建列表的副本(一种“故障安全迭代”形式),但这似乎违反了约束 A。

我需要编写自己的集合和迭代器吗?从头开始?即使在这种情况下,我是否忽略了 CME 实际上是一个有用的异常(exception)?有没有我可以使用的数据结构、策略或模式来代替默认迭代器来解决这个问题?

最佳答案

如果在使用迭代器时修改了迭代器的底层结构,您可以获得 CME——无论并发性如何。事实上,只在一个线程上就很容易得到一个:

List<String> strings = new ArrayList<String>();
Iterator<String> stringsIter = strings.iterator();
strings.add("hello"); // modify the collection
stringsIter.next();   // CME thrown!

确切的语义取决于集合,但通常,CME 会在您创建迭代器后修改集合时出现。 (这是假设迭代器不明确允许并发访问,当然,java.util.concurrent 中的某些集合允许并发访问)。

天真的解决方案是同步整个 while (true) 循环,但当然你不想这样做,因为那样你就锁定了一堆昂贵的操作。

相反,您可能应该复制集合(在锁下),然后对副本进行操作。为了确保事物仍在 things 中,您可以在循环中仔细检查它:

List<Thing> thingsCopy;
synchronized (things) {
    thingsCopy = new ArrayList<Thing>(things);
}
for (Thing thing : thingsCopy) {
    synchronized (things) {
        if (things.contains(thing)) {
            thing.costlyOperation();
        }
    }
}

(您还可以使用上述允许在迭代时进行修改的 java.util.concurrent 集合之一,例如 CopyOnWriteArrayList 。)

当然,现在您仍然拥有围绕coSTLyOperation 的同步块(synchronized block)。您可以将 contains 检查单独移动到同步块(synchronized block)中:

boolean stillInThings;
synchronized (things) {
    stillInThings = things.contains(thing);
}

... 但这只会降低快感,并不会消除它。这对您来说是否足够好取决于您的应用程序的语义。一般来说,如果您希望代价高昂的操作仅在事物在事物中时发生,您需要对其进行某种锁定。

如果是这种情况,您可能需要考虑使用 ReadWriteLock而不是 synchronized block 。这有点危险(因为如果您不小心总是在 finally block 中释放锁,这种语言会让您犯更多错误),但可能是值得的。基本模式是使 thingsCopy 和双重检查在读者锁下工作,而对列表的修改在写者锁下发生。

关于java - 在迭代中避免不必要的 ConcurrentModificationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29240146/

相关文章:

java - 如何从 java 在 Linux 操作系统中执行 .vbs 文件

java - 通过 XMLRPC 访问 Atlassian Confluence 时遇到困难

CPU缓存抑制

java - java中的多线程任务调度器

Java 线程和内核数

java - 有没有办法让 Checkstyle 忽略包含 @link 或 @see 的 javadoc?

java - Arquillian org.jboss.arquillian.container.spi.client.deployment.Validate.isArchiveOfType(Ljava/lang/Class;Lorg/jboss/shrinkwrap/api/Archive;)Z

java - Android-App 消耗大量能量。套接字可能是原因

java - 这是饥饿吗?

java - 使用多线程写入文件