如何从一个线程循环ArrayList
,同时从其他线程添加它?
1)我有一个循环遍历ArrayList
并执行一些检查的线程。
2)我有第二个帖子,它添加到ArrayList
的结尾。
我有信号量,当第二个运行时暂停第一个线程, 但是我该如何实现这个:
当第二个线程向ArrayList
添加内容时,第一个线程将从暂停的位置继续循环而不抛出java.util.ConcurrentModificationException
?
答案 0 :(得分:6)
如果您必须使用ArrayList
,则必须同步对其的访问权限。但是,这仍然无法使您免于ConcurrentModificationExeptions,因为这并不一定与并发有关。
来自JavaDoc
请注意,此异常并不总是表示某个对象已被另一个线程同时修改。如果单个线程发出违反对象合同的一系列方法调用,则该对象可能会抛出此异常。例如,如果线程在使用失败快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。
如果可以确保只有新项目附加到列表中,您可以只使用索引并在其上手动循环而不使用迭代器。一旦到达队列的末尾,就会从头开始。
ArrayList q = new ArrayList<>();
//thread 1
Object o = ...;
synchronized(q) {
q.add(o); //append
}
//thread 2
int i = 0;
synchronized(q) {
int size = q.size();
for(; i < size; i++){
Object o = q.get(i);
//do something with o
}
if(i >= size) {
i = 0;
}
}
但是这样做会导致一个有点顺序的行为,因为一个或另一个线程可能在列表上一次操作。唯一的“优势”是线程模型为循环操作增加了一些随机性。所以你也可以跳过同步和并发而只是做
q.add(o); //append
//you may add a random condition, i.e. time interval, item count random number to trigger the loop process so it don't get exectued on each add
for(Object o : q){
//do something with o
}
当然你可以像Nicolas写的那样使用Safe-Copy,但这只是容器结构(列表)的浅层副本,而不是它的内容。因此,如果您这样做,请确保项目是线程安全的或不要修改它们。
如果第二个线程偶尔从列表末尾删除一个项目,则最好使用队列。 Java为此提供了Deque
。一个线程可以在一端添加元素,而另一个线程从另一端移除。当您在多个线程中使用它并手动同步时,您应该更好地使用线程安全实现并跳过同步:
Deque q = new ConcurrentLinkedDeque<>();
//thread 1
Object o = ...;
q.addFirst(o);
//thread 2
while(!q.isEmpty()){
Object o = q.removeLast();
//do something with o
}
答案 1 :(得分:1)
假设您无法使用Queue
ConcurrentLinkedQueue
,请按以下步骤操作:
我假设您的检查速度很快:
主题1:
synchronized(list) {
for (MyClass obj : list) {
// check what you want here
}
}
主题2:
synchronized(list) {
list.add(obj);
}
我假设您的检查速度很慢:
主题1:
List<MyClass> safeCopy;
synchronized(list) {
safeCopy = new ArrayList<>(list);
}
for (MyClass obj : safeCopy) {
// check what you want here
}
主题2:
与上述相同
如果你不经常修改你的列表,你应该使用CopyOnWriteArrayList
它已经是线程安全的,读操作非常快,因为没有锁定但是写操作很慢,因为它重建了整个列表
答案 2 :(得分:0)
最简单的解决方案是使用CopyonWritearrayList,它设计精确地用于这种东西,但请注意它比平常的ArrayList慢