正在检测并发修改?
在我正在处理的多线程应用程序中,我们偶尔会在列表中看到" ConcurrentModificationExceptions"(主要是" ArrayList",有时是Vector)。但是有时我认为并发修改正在发生,因为遍历集合似乎缺少了项,但是没有抛出异常。我知道ConcurrentModificationException的文档说我们不能依赖它,但是我将如何确保不同时修改List?并且将对集合的所有访问都包装在同步块中是防止它的唯一方法吗?
更新:是的,我知道Collections.synchronizedCollection
,但是它不能防止有人在遍历集合时修改集合。我认为,当我在遍历集合时向集合中添加某些内容时,至少会发生我的一些问题。
第二次更新如果有人想像Jason一样提到提到的synchronizedCollection和克隆,再加上像jacekfoo和Javamann这样提到的apache collections框架,我可以接受一个答案。
解决方案
回答
请查看java.util.concurrent,了解旨在更好地处理并发性的标准Collections类的版本。
回答
我们可以尝试使用防御性复制,以使对一个"列表"的修改不会影响其他列表。
回答
是的,我们必须同步对集合对象的访问。
或者,我们可以在任何现有对象周围使用同步包装。请参见Collections.synchronizedCollection()。例如:
List<String> safeList = Collections.synchronizedList( originalList );
但是,所有代码都需要使用安全版本,即使在另一个线程修改时进行迭代也会导致问题。
要解决迭代问题,请首先复制列表。例子:
for ( String el : safeList.clone() ) { ... }
对于更优化的,线程安全的集合,还请查看java.util.concurrent。
回答
Collections.synchronizedList()将名义上呈现一个线程安全的列表,并且java.util.concurrent具有更强大的功能。
回答
通常,如果要在迭代过程中尝试从列表中删除一个元素,则会收到ConcurrentModificationException。
最简单的测试方法是:
List<Blah> list = new ArrayList<Blah>(); for (Blah blah : list) { list.remove(blah); // will throw the exception }
我不确定我们将如何解决。我们可能必须实现自己的线程安全列表,或者可以创建原始列表的副本进行写入,并具有一个写入该列表的同步类。
回答
在同步块中包装对集合的访问是执行此操作的正确方法。在处理跨多个线程共享的状态时,标准的编程实践要求使用某种锁定机制(信号量,互斥量等)。
但是,根据用例,通常可以进行一些优化以仅在某些情况下锁定。例如,如果我们有一个经常被读取但很少被写入的集合,那么我们可以允许并发读取,但是只要正在进行写入就强制执行锁定。如果集合正在被修改,则并发读取只会导致冲突。
回答
ConcurrentModificationException是尽力而为的,因为我们要问的是一个难题。除了证明访问模式不会同时修改列表之外,没有在不牺牲性能的情况下可靠地执行此操作的好方法。
同步可能会阻止并发修改,这最终可能是我们要采取的措施,但最终可能会付出高昂的代价。最好的办法可能是坐下来思考一下算法。如果我们无法提出无锁解决方案,请诉诸同步。
回答
根据更新频率,我的最爱之一是CopyOnWriteArrayList或者CopyOnWriteArraySet。他们在更新时创建新的列表/集,以避免并发修改异常。
回答
参见实施。它基本上存储一个int:
transient volatile int modCount;
并且在存在"结构修改"(例如remove)时增加。如果迭代器检测到modCount已更改,则它将引发并发修改异常。
同步(通过Collections.synchronizedXXX)不会做得很好,因为它不能保证迭代器的安全性,它只能通过put,get,set来同步写入和读取操作。
请参见java.util.concurennt和apache collections框架(它具有一些经过优化的类,它们在读取(非同步)次数多于写入次数时,可以在并发环境中正常工作,请参见FastHashMap。
回答
这将消除并发修改异常。但是我不会讲效率;)
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);
回答
我们最初的问题似乎是要求一个迭代器,该迭代器在保持线程安全的同时查看对基础集合的实时更新。在一般情况下,这是一个难以解决的昂贵问题,这就是为什么没有标准集合类可以做到这一点的原因。
有很多方法可以部分解决问题,在应用程序中,其中一种可能就足够了。
Jason提供了一种特定的方法来实现线程安全,并避免引发ConcurrentModificationException,但这只是以牺牲活动性为代价。
Javamann在java.util.concurrent包中提到了两个特定的类,它们以无锁的方式解决了相同的问题,在这种情况下,可伸缩性至关重要。这些仅随Java 5一起提供,但是有许多项目将软件包的功能反向移植到早期的Java版本中,包括该版本,尽管它们在早期的JRE中表现不佳。
如果我们已经在使用某些Apache Commons库,那么正如jacekfoo指出的那样,apache collections框架包含一些有用的类。
我们也可以考虑查看Google收藏夹框架。
回答
我们还可以在列表中的Iteratins上进行同步。
List<String> safeList = Collections.synchronizedList( originalList ); public void doSomething() { synchronized(safeList){ for(String s : safeList){ System.out.println(s); } } }
这将锁定列表,使其处于同步状态,并阻止所有试图在编辑或者迭代列表时访问该列表的线程。缺点是我们会创建瓶颈。
这样可以节省.clone()方法的一些内存,并且可能会更快,具体取决于我们在迭代中正在执行的操作...