在很多情况下,我需要设置一个List
,然后以循环方式遍历它们。我对多线程技术还比较陌生,所以我正在学习。
今天我开始遇到一些并发问题。
Exception in thread "LogThing: 25" Exception in thread "LogThing: 27" Exception in thread "LogThing: 21" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.google.common.collect.Iterators$2.next(Iterators.java:418)
at com.example.my.RoundRobinIterable.getNext(RoundRobinIterable.java:22)
at com.example.my.EpsSendingStrategy.run(EpsSendingStrategy.java:22)
at java.lang.Thread.run(Thread.java:745)
现在,RoundRobbinIterable非常简单。
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Iterables;
public class RoundRobinIterable<T> {
private final Iterator<T> elements;
public RoundRobinIterable(final List<T> elements) {
this.elements = Iterables.cycle(elements).iterator();
}
public synchronized T getNext() {
return this.elements.next();;
}
}
现在EpsSendingStrategy.java具有一个static RoundRobinIterable<PrintWriter> writerIterable
,并且您可以从stacktrace中看到它也是一个线程。创建RoundRobinIterable
时,我将一些值传递给Collections.synchronizedList(new ArrayList<>())
。第22行恰好是writer = writerIterable.getNext();
所以getNext()
导致java.util.ConcurrentModificationException
异常
编辑1:
public class EpsSendingStrategy extends SendingStrategy {
public EpsSendingStrategy(LogDosSettings settings) {
super(settings);
logger = LogManager.getLogger(EpsSendingStrategy.class);
}
@Override
public void run() {
createConnections();
startSending.set(System.nanoTime());
PrintWriter writer;
while (sendMessages) {
logger.debug("about to write message");
writer = writerIterable.getNext();
writer.println(settings.getMessage());
writer.flush();
lastSent.set(startSending.get() + logItoration.get() * 1000000000L / settings.getEps());
while (System.nanoTime() < lastSent.get());
logItoration.incrementAndGet();
}
}
}
编辑2:我也尝试过在https://stackoverflow.com/a/4493759/2599884上使用相同的同步,但这也很失败。还有我不了解的更深层次的东西。
答案 0 :(得分:1)
如Collections.synchronizedList()的javadoc中所述:-
Iterator i = list.iterator(); // Must be in synchronized block
但是您要在这一行中将同步列表传递给番石榴:-
this.elements = Iterables.cycle(elements).iterator();
其中I think在调用.iterator()
时不使用同步。另外,它可以在不受您控制的时间调用它(例如您对其进行修改时)。
尽管此答案可能会为您指明正确的方向,但值得等待某个人提供更明确的答案(我从未使用过番石榴)。
答案 1 :(得分:1)
ConcurrentModificationException 是因为您在某些线程将元素添加到列表中时在列表上进行了迭代。
您正在使用 Collections.synchronizedList ,其中添加和删除方法通过列表锁进行同步。要禁止其他人在使用迭代器迭代列表时向列表添加内容,您必须像这样锁定该列表。
synchronized(list){
Iterator i = list.iterator();
while (i.hasNext()){
//do something
}
}
您没有执行此操作,您正在同步 getNext 方法,但没有使用列表锁,并且仅在调用 next()方法时才进行同步。足够。
public synchronized T getNext() {
return this.elements.next();;
}
如果要使用模式,则必须在RoundRobinIterable中创建列表的副本
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Iterables;
public class RoundRobinIterable<T> {
private final Iterator<T> elements;
public RoundRobinIterable(final List<T> elements) {
List<T> copyOfElements = new ArrayList<>(elements);
this.elements = Iterables.cycle(copyOfElements).iterator();
}
public synchronized T getNext() {
return this.elements.next();;
}
}
现在,在迭代列表时,没有人可以将元素添加到复制的列表中,但是创建RoundRobinIterable之后,您将不会获得添加到原始列表中的元素。如果要获取稍后添加的元素,那么最好使用除List之外的其他方法。例如 ConcurrentLinkedQueue。