1

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

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

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

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

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

4

4 回答 4

2

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

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

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

于 2012-07-19T04:39:11.327 回答
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
    }
}
于 2012-07-19T13:58:26.500 回答
0

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

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

A -> B -> C -> D

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

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

于 2012-07-19T04:31:54.833 回答
0

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

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

你能分成N个列表吗?

于 2012-07-19T04:11:39.197 回答