在我正在开发的多线程应用程序中,我们偶尔会看到 ConcurrentModificationExceptions 在我们的名单上(主要是 ArrayList, ,有时是向量)。但有时我认为正在发生并发修改,因为迭代集合似乎缺少项目,但不会引发异常。我知道文档 ConcurrentModificationException 说你不能依赖它,但我如何确保我不会同时修改列表?将对集合的每次访问包装在同步块中是防止这种情况的唯一方法吗?

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

第二次更新 如果有人想将synchronizedCollection和克隆(像Jason那样)与java.util.concurrent和apache集合框架(如jacekfoo和Javamann)结合起来,我可以接受答案。

有帮助吗?

解决方案

您最初的问题似乎是要求一个迭代器,该迭代器可以在保持线程安全的同时查看底层集合的实时更新。在一般情况下,解决这个问题的成本极其高昂,这就是为什么没有一个标准集合类能做到这一点。

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

杰森给出 实现线程安全并避免抛出 ConcurrentModificationException 的特定方法, ,但只能以牺牲活性为代价。

爪哇曼提到 两个特定的类 在里面 java.util.concurrent 以无锁方式解决相同问题的包,其中可扩展性至关重要。这些仅随 Java 5 一起提供,但有各种项目将包的功能反向移植到早期的 Java 版本中,包括 这个, ,尽管它们在早期的 JRE 中不会有如此好的性能。

如果您已经在使用某些 Apache Commons 库,则使用 jacekfoo 指出, , 这 Apache 集合框架 包含一些有用的课程。

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

其他提示

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

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

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

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

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

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

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

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

对于更优化、线程安全的集合,另请参阅 java.util.concurrent.

如果您在迭代列表时尝试从列表中删除元素,通常会收到 ConcurrentModificationException。

测试这一点的最简单方法是:

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

我不确定你会如何解决它。您可能必须实现自己的线程安全列表,或者您可以创建原始列表的副本以供写入,并拥有一个写入列表的同步类。

您可以尝试使用防御性复制,以便对某个内容进行修改 List 不要影响别人。

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

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

ConcurrentModificationException 是尽力而为的,因为您要问的是一个难题。除了证明您的访问模式不会同时修改列表之外,没有什么好方法可以在不牺牲性能的情况下可靠地完成此操作。

同步可能会阻止并发修改,这可能是您最终所采用的方法,但最终可能会代价高昂。最好的办法可能是坐下来思考一下你的算法。如果你无法想出无锁的解决方案,那么就求助于同步。

参见实施。它基本上存储一个 int:

transient volatile int modCount;

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

同步(通过 Collections.synchronizedXXX)效果不佳,因为它不能保证迭代器安全,它只能通过 put、get、set 同步写入和读取...

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

您还可以通过列表上的迭代进行同步。

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

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

     }
   }

}

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

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

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

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

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);
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top