检测并发修改?

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

更新:是的,我知道Collections.synchronizedCollection ,但它不会防止有人在你迭代它时修改集合。 我认为至少有一些问题发生在我正在迭代它时有人向集合添加内容时。

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

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

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

Jason提供了一种实现线程安全的特定方法,并避免抛出ConcurrentModificationException ,但仅以牺牲活力为代价。

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

如果您已经在使用某些Apache Commons库,那么正如jacekfoo所指出的那样 , apache集合框架包含一些有用的类。

您也可以考虑查看Google集合框架 。

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

查看java.util.concurrent,了解设计用于更好地处理并发性的标准Collections类的版本。

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

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

 List safeList = Collections.synchronizedList( originalList ); 

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

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

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

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

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

最简单的测试方法是:

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

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

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

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

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

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

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

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

 transient volatile int modCount; 

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

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

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

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

 List safeList = Collections.synchronizedList( originalList ); public void doSomething() { synchronized(safeList){ for(String s : safeList){ System.out.println(s); } } } 

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

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

Collections.synchronizedList()将呈现名义上为线程安全的列表,并且java.util.concurrent具有更强大的function。

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

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