ArrayList<IFoo> objs = new ArrayList<>();
public void add(IFoo foo) {
objs.add(foo);
}
public void loop() {
objs.forEach(obj -> {
obj.doSomething(message);
});
}
这种方法容易出现ConcurrentModificationException错误或
我应该制作数组的副本
我应该创建一个迭代器吗?
或者我应该同步标记我的两种方法吗?
答案 0 :(得分:2)
好吧所以我把所有这些都放在评论中,但我觉得这太长了不能放在那里所以这里是一个答案。
你所拥有的是简单的做法
for(Object obj: objs){ ... }
反过来又是获取迭代器并迭代集合的简便方法。处理集合的Iterator
负责抛出异常。 ArrayList的迭代器确实会抛出一个ConcurrentModificationException,而迭代器在修改后会被访问。因此,如果您的代码正在执行此操作,那么是的。否则,没有。
注意:ConcurrentModificationException
虽然包含单词Concurrent并不一定与多线程有关。这是一个非常重要的区别。只需执行此操作即可获得ConcurrentModificationException
:
objs.forEach(obj -> {objs.remove(0)});
确实,执行此操作的多个Thread
也可能导致此问题,但该异常实际上与多线程无关。 ConcurrentModificationException
只是意味着ArrayList
在被访问时被同时修改,其中此上下文中的并发意味着同时意味着多线程的更传统的计算含义。
编辑:
我看到了你对保护它的评论,还看到了另一个关于它在你无法控制的lib中的评论。真正保护它的最好方法可能是在这种情况下复制List
。问题是lib是一个黑盒子,你不能控制它们是否修改集合,更恰当地说,它们是否阻止/锁定/控制它们从集合中插入/删除。可能创建List的浅表副本就足够了。见Collections#copy(List, List)
或ArrayList#ArrayList(Collection)
答案 1 :(得分:1)
如果您修改ArrayList
或其他线程中的doSomething
,则会抛出这种情况,因为任何形式的迭代都应该。
但如果你不这样做,那就没理由担心了。
答案 2 :(得分:0)
是的,如果您正在修改列表,例如添加或删除元素。但是,如果您要更改迭代对象的值/属性,那么您应该很好(正如您所尝试的那样)。
下面以传统循环和使用Lambda的ConcurrentModificationException为例,两者都容易出现故障。
import java.util.ArrayList;
import java.util.List;
public class Temp {
public static void main(String[] args) {
List<Integer> l = new ArrayList<>();
l.add(1);
for(Integer data :l){
l.add(99);
}
l.forEach(obj -> {
l.add(99);
});
}
}
对于您担心修改集合的其他线程的情况,请创建要迭代的列表副本,使用Collections api复制数组。