【问题标题】:LinkedList Iterator throwing Concurrent Modification ExceptionLinkedList 迭代器抛出并发修改异常
【发布时间】:2012-07-19 03:36:41
【问题描述】:

有没有办法阻止 ListIterator 抛出 ConcurrentModificationException?这就是我想做的:

  • 用一堆对象创建一个 LinkedList,这些对象有一个要经常执行的特定方法。
  • 拥有一定数量的线程(比如N个),所有这些线程都负责执行LinkedList中对象的上述方法。例如,如果列表中有 k 个对象,线程 n 将执行列表中第 n 个对象的方法,然后继续执行第 n+N 个对象,然后执行第 n+2N 个等,直到它循环回到开头。

这里的问题在于这些对象的检索。我显然会使用 ListIterator 来完成这项工作。但是,我预测这不会走得太远,这要归功于根据文档将抛出的 ConcurrentModificationException。我希望列表是可修改的,并且迭代器不在乎。事实上,预计这些对象会创建和销毁列表中的其他对象。 我想到了一些解决方法:

  • 创建和销毁一个新的迭代器以检索给定索引处的对象。但是,这是 O(n),不可取。
  • 请改用 ArrayedList;然而,这也是不可取的,因为删除是 O(n) 并且列表需要不时扩展(也许是收缩?)存在问题。
  • 编写我自己的 LinkedList 类。不想。

因此,我的问题。有没有办法阻止 ListIterator 抛出 ConcurrentModificationException?

【问题讨论】:

  • 为什么不考虑使用 BlockingQueue - docs.oracle.com/javase/6/docs/api/java/util/concurrent/…
  • 为什么一定要使用迭代器?
  • 我不确定 BlockingQueue 将如何解决我的问题...能够检索列表中的任意索引至关重要,因此队列不会这样做...
  • 我“必须”使用迭代器,因为这是我所知道的唯一可以用来遍历原生 Java LinkedList 的方法。如果有其他方法可以做到这一点,请分享。
  • @user1536654 您多久修改一次列表(添加、设置、删除...)?

标签: java multithreading linked-list concurrentmodification


【解决方案1】:

您似乎关心性能。您是否实际测量过使用 O(n) 与 O(1) 算法对性能的影响?根据您正在做什么以及您执行它的频率,简单地使用线程安全的CopyOnWriteArrayList 可能是可以接受的。它的迭代器也是线程安全的。

主要的性能拖累是可变操作(设置、添加、删除...):每次都会重新创建一个新列表。

但是,对于大多数应用程序来说,性能已经足够好了。我个人会尝试使用它,分析我的应用程序以检查性能是否足够好,如果是就继续。如果不是,您将需要寻找其他方法。

【讨论】:

  • 我还没有到实现这个的开发阶段,所以我没有做任何性能测试。但是,我想这种差异对于数千个对象来说是显而易见的。
  • 我问这个问题是为了获得帮助做出先发制人的设计选择。但我想最好自己做实验。也感谢您的其他建议。 :)
  • 如果它是你每秒遍历的几个 1000 个对象,你应该不会注意到它。如果每 10 毫秒有 100,000 个对象,那么您可能需要找到其他东西。但是生产者/消费者模式似乎适用于您的用例。
【解决方案2】:

有没有办法阻止 ListIterator 抛出 ConcurrentModificationException?

您以这种方式提出这个问题表明您对如何正确使用线程来提高应用程序的性能缺乏了解。

使用线程的全部目的是将处理和 IO 划分为独立的可运行实体,这些实体可以并行执行——相互独立。如果您将所有线程分叉以在同一个 LinkedList 上工作,那么您很可能会获得性能损失或最小增益,因为保持每个线程的“视图”所需的同步开销同步的LinkedList 将抵消并行执行带来的任何收益。

问题应该是“我如何停止ConcurrentModificationException”,而应该是“我如何使用线程来改进对象列表的处理”。这是正确的问题。

要与多个线程并行处理对象集合,您应该使用ExecutorService 线程池。您可以使用类似于以下代码的内容创建池。 LinkedList(在本例中为 Job)中的每个条目将由池中的线程并行处理。

// create a thread pool with 10 workers
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// submit each of the objects in the list to the pool
for (Job job : jobLinkedList) {
    threadPool.submit(new MyJobProcessor(job));
}
// once we have submitted all jobs to the thread pool, it should be shutdown
threadPool.shutdown();
// wait for the thread-pool jobs to finish
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
synchronized (jobLinkedList) {
    // not sure this is necessary but we need to a memory barrier somewhere
}
...
// you wouldn't need this if Job implemented Runnable
public class MyJobProcessor implements Runnable {
    private Job job;
    public MyJobProcessor(Job job) {
        this.job = job;
    }
    public void run() {
        // process the job
    }
}

【讨论】:

  • 非常感谢您的意见。我认为我将获得性能提升,因为与实际对象进程相比,LinkedList 访问是一项非常容易的任务。但也许不是……我想当我深入研究时,我将不得不为自己做实验。我会记住你的建议。再次非常感谢您。
【解决方案3】:

您可以使用Iterator 来扫描列表,并使用Executor 通过传递到线程池来对每个对象执行工作。这很容易。以这种方式打包工作单元是有开销的。您仍然必须小心使用Iterator 方法来修改列表,但这可能会简化问题。

或者你可以一次完成你的工作,然后在下一次列出修改?

你能分成 N 个列表吗?

【讨论】:

  • 我不太确定我是否理解正确,所以如果我没有理解,请原谅我。但我认为这基本上就是我正在做的事情。我将拥有一个您所说的线程池,以及一个由迭代器扫描的“任务”列表。问题是,任务列表需要是可修改的:任何线程都应该允许添加和删除任务。根据文档,此时迭代器将失败并引发异常。
  • 至于一次执行工作,然后在下一次列出修改,我认为这会对性能产生不利影响。我认为我负担不起那样做。拆分为 N 个列表也是我的想法,但它会给我的系统带来其他缺点,最后我决定如果遇到最坏的情况,我宁愿编写自己的 LinkedList,而不是拆分列表。不过感谢您的建议。感谢您的宝贵时间。
  • 在我的第一条评论中,我建议在一个线程中使用一个Iterator,但将工作分配给其他线程。这与许多线程中的许多Iterators 不同,也许更简单。
  • 啊,我明白你的意思了。那更简单。但是,据我所知,恐怕它无法解决该异常,因为该列表仍将被外部修改。对吗?
【解决方案4】:

请看@assylias 的回答——他的建议很好。我要补充的是,如果您决定编写自己的链表类,则需要非常仔细地考虑如何使其成为线程安全的。

想一想如果多个线程同时尝试修改列表,您的列表可能会被破坏的所有方式。仅仅锁定 1 或 2 个节点是不够的——例如,以下面的列表为例:

A -> B -> C -> D

想象一个线程试图删除 B,就像另一个线程正在删除 C。要删除 B,从 A 的链接需要“跳过”B 到 C。但是如果 C 不再是列表的一部分怎么办?那时?同样,要删除 C,需要将来自 B 的链接更改为跳转到 D,但如果此时 B 已经从列表中删除了怎么办?当节点同时添加到列表的附近部分时,也会出现类似的问题。

如果每个节点有 1 个锁,并且在执行“删除”操作时锁定 3 个节点(要删除的节点,以及它之前和之后的节点),我认为这将是线程安全的。您还需要仔细考虑在添加节点以及遍历列表时必须锁定哪些节点。为了避免死锁,您需要确保始终以恒定的顺序获取锁,并且在遍历列表时,您需要使用“hand-over-hand”锁定(这排除了使用普通 Java 监视器 - 您需要显式锁定对象)。

【讨论】:

  • 哈哈。当 Java 附带的列表完全可用时,我不想编写自己的列表的原因之一。如果我必须编写自己的课程,我 90% 肯定我第一次会搞砸。感谢您指出。
  • 构建正确、健壮、高性能的多线程代码可能非常非常复杂。在您考虑尝试构建自己的高并发列表实现之前,您应该退后一步,质疑您是否可以使用不同的方法实现预期的结果。
猜你喜欢
  • 2011-01-16
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2014-10-08
相关资源
最近更新 更多