两个线程访问经常更新的Arraylist的问题

我有存储许多对象的ArrayLists,并且经常在ArrayLists中添加和删除对象。 一个线程对数据结构进行操作,并每20ms左右更新一次ArrayList的对象。 另一个线程遍历ArrayLists并使用它们的元素绘制对象(也是每20-30ms)。

如果使用for循环遍历ArrayLists,则IndexOutOfBoundsExceptions比比皆是。 如果使用迭代器遍历ArrayLists,则ConcurrentModificationExceptions比比皆是。 像这样同步ArrayLists:

List list = Collections.synchronizedList(new ArrayList()); synchronized(list) { //use iterator for traversals } 

没有例外,但有大量的性能消耗。 有没有办法遍历这些ArrayLists而没有exception抛出,没有性能消耗?

谢谢!

解决此问题的一个好方法是使线程在列表的不同副本上工作。 但是, CopyOnWriteArrayList不适合这里,因为它会在每次修改时创建一个副本,但在您的情况下,创建副本的频率会更低。

因此,您可以手动实现它:第一个线程创建更新列表的副本并通过volatile变量发布它,第二个线程使用此副本(我假设第一个线程仅修改列表,而不是其中的对象):

 private volatile List publicList; // Thread A List originalList = ...; while (true) { modifyList(originalList); // Modify list publicList = new ArrayList(originalList); // Pusblish a copy } // Thread B while (true) { for (Object o: publicList) { // Iterate over a published copy ... } } 

在迭代之前将ArrayList复制到新变量中怎么样? 这样,您只需要同步副本块而不是列表的整个迭代。

您是否尝试过使用Iterator并使用CopyOnWriteArrayList ? 保证不会抛出ConcurrentModificationException

来自Oracle javadocs(重点补充):

ArrayList的线程安全变体,其中所有可变操作(添加,设置等)都是通过创建底层数组的新副本来实现的。

这通常成本太高, 但是当遍历操作大大超过突变时它可能比替代方法更有效,并且在您不能或不想同步遍历但需要排除并发线程之间的干扰时非常有用 。 “快照”样式迭代器方法在创建迭代器时使用对数组状态的引用。 这个数组在迭代器的生命周期中永远不会改变,所以干扰是不可能的,并且保证迭代器不会抛出ConcurrentModificationException 。 自迭代器创建以来,迭代器不会反映列表的添加,删除或更改。 不支持对迭代器本身进行元素更改操作(删除,设置和添加)。 这些方法抛出UnsupportedOperationException。

创建CopyOnWriteArrayList类是为了解决此问题。

您可以使用CopyOnWriteArrayList,它不会获得ConcurrentModificationException,也不需要同步,或者您可以执行类似的操作。

 List list = Collections.synchronizedList(new ArrayList()); List copy; // lock the list for the minimal amount of time. synchronized(list) { copy = new ArrayList(list); } // use the copy of the array list. 

BTW CopyOnWriteArrayList看起来像

 List list = new CopyOnWriteArrayList(); // use the list. 

虽然CopyOnWriteArrayList为读者提供了最大的性能,但如果写入频繁,则会出现写入器性能问题。

如果您的访问模式只是通过迭代器而您没有进行任何随机访问,那么使用队列可能是更好的选择,因为您可以使用ConcurrentLinkedQueue之类的东西。 请参阅例如JAVA:用于访问java中列表的并发控制 。