检测并发修改?

时间:2008-09-16 17:49:22

标签: java collections

在我正在处理的多线程应用程序中,我们偶尔会在列表中看到ConcurrentModificationExceptions(主要是ArrayList,有时候是Vector)。但是还有一些时候我认为并发修改正在发生,因为遍历集合似乎缺少项目,但没有抛出异常。我知道ConcurrentModificationException的文档说你不能依赖它,但是我怎样才能确保我不会同时修改List?并且在同步块中包装对集合的每次访问是阻止它的唯一方法吗?

更新:是的,我知道Collections.synchronizedCollection,但它并不能防止有人在您重复修改集合时修改集合。我认为至少有一些问题发生在我正在迭代它的时候有人给集合添加了东西。

第二次更新如果有人想结合提到的synchronizedCollection和像Jason那样的克隆,提到java.util.concurrent和像jacekfoo和Javamann这样的apache集合框架,我可以接受答案。

12 个答案:

答案 0 :(得分:6)

根据您的更新频率,我最喜欢的一个是CopyOnWriteArrayList或CopyOnWriteArraySet。他们在更新时创建新的列表/集以避免并发修改异常。

答案 1 :(得分:4)

您的原始问题似乎是要求迭代器在保持线程安全的同时查看底层集合的实时更新。在一般情况下,这是一个非常昂贵的问题需要解决,这就是为什么标准集合类都没有这样做的原因。

有很多方法可以实现问题的部分解决方案,在您的应用程序中,其中一个可能就足够了。

Jason给出了a specific way to achieve thread safety, and to avoid throwing a ConcurrentModificationException,但这只是牺牲了活力。

Javamann在java.util.concurrent包中提到two specific classes以无锁方式解决同一问题,其中可伸缩性至关重要。这些只与Java 5一起提供,但是已经有各种项目将软件包的功能向后移植到早期的Java版本中,包括this one,尽管它们在早期的JRE中不会有这么好的性能。

如果您已经在使用某些Apache Commons库,那么作为jacekfoo points outapache collections framework包含一些有用的类。

您也可以考虑查看Google collections framework

答案 2 :(得分:3)

查看java.util.concurrent,了解那些旨在更好地处理并发性的标准集合类的版本。

答案 3 :(得分:3)

是的,您必须同步对集合对象的访问权。

或者,您可以在任何现有对象周围使用synchronized包装器。见Collections.synchronizedCollection()。例如:

List<String> safeList = Collections.synchronizedList( originalList );

但是所有代码都需要使用安全版本,即使如此迭代,而另一个线程修改也会导致问题。

要解决迭代问题,请先复制列表。例如:

for ( String el : safeList.clone() )
{ ... }

对于更优化的线程安全集合,请查看java.util.concurrent

答案 4 :(得分:2)

如果您在迭代过程中尝试从列表中删除元素,通常会出现ConcurrentModificationException。

最简单的测试方法是:

List<Blah> list = new ArrayList<Blah>();
for (Blah blah : list) {
     list.remove(blah); // will throw the exception
}

我不确定你是怎么解决这个问题的。您可能必须实现自己的线程安全列表,或者您可以创建原始列表的副本以进行写入,并具有写入列表的同步类。

答案 5 :(得分:1)

您可以尝试使用防御性复制,以便对一个List的修改不会影响其他人。

答案 6 :(得分:1)

在同步块中包装对集合的访问是正确的方法。标准编程实践规定在处理多个线程共享的状态时使用某种锁定机制(信号量,互斥量等)。

根据您的使用情况,您通常可以进行一些优化,仅在某些情况下锁定。例如,如果您有一个经常读取但很少写入的集合,那么您可以允许并发读取,但只要写入正在进行就强制执行锁定。如果集合正在被修改过程中,并发读取只会导致冲突。

答案 7 :(得分:1)

ConcurrentModificationException是最好的努力,因为你问的是一个难题。除了证明您的访问模式不会同时修改列表之外,没有好的方法可靠地执行此操作而不牺牲性能。

同步可能会阻止并发修改,它可能是你最终要求的,但最终可能会造成代价高昂。最好的办法是坐下来思考一下你的算法。如果您无法提供无锁解决方案,那么请求同步。

答案 8 :(得分:1)

查看实施情况。它基本上存储了一个int:

transient volatile int modCount;

并且当存在“结构修改”(如删除)时会增加。如果迭代器检测到modCount已更改,则抛出并发修改异常。

同步(通过Collections.synchronizedXXX)不会有好处,因为它不保证迭代器安全,它只通过put,get,set ...同步写入和读取...

请参阅java.util.concurennt和apache集合框架(当有更多读取(不同步)而不是写入时,它有一些优化的类在并发环境中正常工作 - 请参阅FastHashMap。

答案 9 :(得分:1)

您还可以在列表上通过iteratins进行同步。

List<String> safeList = Collections.synchronizedList( originalList );

public void doSomething() {
   synchronized(safeList){
     for(String s : safeList){
           System.out.println(s);

     }
   }

}

这将锁定同步列表,并在编辑或迭代时阻止尝试访问列表的所有线程。缺点是你制造了一个瓶颈。

这会节省一些.clone()方法的内存,并且可能会更快,具体取决于你在迭代中所做的事情...

答案 10 :(得分:-1)

Collections.synchronizedList()将呈现一个名义上是线程安全的列表,java.util.concurrent具有更强大的功能。

答案 11 :(得分:-1)

这将消除您的并发修改异常。但我不会谈论效率;)

List<Blah> list = fillMyList();
List<Blah> temp = new ArrayList<Blah>();
for (Blah blah : list) {
     //list.remove(blah);  would throw the exception
     temp.add(blah);
}
list.removeAll(temp);