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

时间:2011-04-11 18:21:35

标签: java multithreading data-structures iterator 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而没有抛出异常,并且没有性能消耗?

谢谢!

6 个答案:

答案 0 :(得分:4)

解决此问题的一个好方法是使线程在列表的不同副本上工作。但是,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
        ...
    }
}

答案 1 :(得分:2)

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

来自Oracle javadocs(强调添加):

  

ArrayList的线程安全变体   所有变异操作(添加,   设置,等等)由实现   制作基础的新副本   阵列。

     

这通常成本太高,但可能   比替代品更有效率   当遍历操作时   超过突变,并且是有用的   什么时候你不能或不想   同步遍历,但需要   排除并发之间的干扰   线程。 “快照”样式迭代器   方法使用对状态的引用   在该点的数组   迭代器已创建。这个数组从不   在生命周期中的变化   迭代器,所以干扰是   不可能和迭代器是   保证不扔   ConcurrentModificationException的即可。该   迭代器不会反映添加内容,   从那以后删除或更改列表   迭代器已创建。   改变元素的操作   迭代器本身(删除,设置和   add)不受支持。这些方法   抛出UnsupportedOperationException。

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

您可以使用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.

答案 5 :(得分:1)

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

如果您的访问模式只是通过迭代器而您没有进行任何随机访问,那么使用队列可能是更好的选择,因为您可以使用ConcurrentLinkedQueue之类的东西。请参阅示例JAVA: Concurrency control for access to list in java