我正在处理的代码是抛出上述异常。我对多线程编程不是很有经验,而且我没有很多运气对此进行故障排除。
该程序使用Processing和OSC以Java编写。主要的OSC事件处理程序是向Vector添加元素。它是在用户输入时触发的,因此非常难以预测。此Vector也在Processing的动画线程中进行迭代和更新,这种线程每秒约60次非常频繁地发生。
偶尔会调用OSC事件处理程序,因为Vector正在动画线程中进行迭代,并抛出异常。
我尝试将“synchronized
”修饰符添加到OSC事件处理程序中。我还试图提示对Vector的更改,直到动画线程的下一帧(时间步),但我发现它最终会延迟抛出的异常。
我该怎么做才能防止这种行为?有没有办法只访问Vector,如果它还没有被使用?
更新 两个答案表明该列表在迭代时添加或删除了元素。这实际上是由于OSC正在从迭代列表的线程以外的线程触发处理程序的事实。我正在寻找一种方法来阻止这种情况。
这是一些伪代码:
Vector<String> list = new Vector<String>();
Vector<Particle> completedParticles = new Vector<Particle>();
public void oscEvent( OSCMessage message )
{
list.add( new Particle( message.x, message.y ) );
}
public void draw()
{
completedParticles.clear();
for( Particle p : list )
{
p.draw();
if( p.isComplete ) {
completedParticles.add( p );
}
}
list.removeAll( completedParticles );
}
答案 0 :(得分:6)
在你的代码中,你的for-each循环遍历列表,你的osEvent
修改了列表。同时运行的两个线程可能会尝试:迭代列表,而其他线程则向其添加元素。你的for循环创建了一个迭代器。
您可以执行以下操作(前提是这些是发生这种情况的唯一两个地方):
//osEvent
synchronized(this.list) {
list.add( new Particle( message.x, message.y ) );
}
//draw
synchronized(this.list) {
for( Particle p : list )
{
p.draw();
if( p.isComplete ) {
completedParticles.add( p );
}
}
}
或者,正如我在下面解释的那样,在迭代之前制作一个矢量副本,这可能会更好。
此异常不一定在多线程代码中抛出。当您在迭代时修改集合时会发生这种情况。即使在单线程应用程序中,您也可以获得此异常。例如,在for-each循环中,如果您删除或添加元素到列表,最终会得到ConcurrentModificationException
。
因此,向代码添加同步不一定能解决问题。一些替代方法包括制作要迭代的数据的副本,或使用接受修改的迭代器(即ListIterator)或带有快照迭代器的集合。
显然,在多线程代码中,您仍然需要注意同步以避免进一步的问题。
让我举几个例子:
假设您希望在迭代时删除集合中的项目。避免ConcurrentModificationException
的替代方法是:
List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
在增强的for循环中收集要删除的所有记录,并在完成迭代后删除所有找到的记录。
ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
if(book.getIsbn().equals(isbn)){
found.add(book);
}
}
books.removeAll(found);
或者您可以在迭代过程中使用支持删除/添加方法的ListIterator
。
ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}
在多线程环境中,您可以考虑在迭代之前制作集合的副本,这样,允许其他人修改原始集合而不影响迭代:
synchronized(this.books) {
List<Book> copyOfBooks = new ArrayList<Book>(this.books)
}
for(Book book : copyOfBooks) {
System.out.println(book);
}
或者,您可以考虑使用快照迭代器使用其他类型的集合,例如java.util.ConcurrentCopyOnWriteArrayList
,它保证不抛出ConcurrentModificationException
。但首先阅读文档,因为这种类型的集合并不适合所有场景。
答案 1 :(得分:2)
如果您想要独占访问权限,则需要锁定列表中的整个操作。 Vector在内部同步,但它仍会释放锁定,然后在迭代的每次传递中再次获取它。
java.util.concurrent.locks.Lock lock = new java.util.concurrent.locks.ReentrantLock();
Vector<String> list = new Vector<String>();
Vector<Particle> completedParticles = new Vector<Particle>();
public void oscEvent( OSCMessage message )
{
lock.lock();
try {
list.add( new Particle( message.x, message.y ) );
} finally {
lock.unlock();
}
}
public void draw()
{
completedParticles.clear();
lock.lock();
try {
for( Particle p : list )
{
p.draw();
if( p.isComplete ) {
completedParticles.add( p );
}
}
list.removeAll( completedParticles );
} finally {
lock.unlock();
}
}
答案 2 :(得分:1)
如果在迭代期间从集合中添加/删除项目,则会发生这种情况。你可以研究一些事情:
答案 3 :(得分:1)
正如已经提到的,当您迭代列表并在迭代期间更改内容时,会发生并发修改异常。对于使用集合的Iterator的某些类将解决此问题但不是所有集合实现Iterator。
尝试使用composition来包装迭代的集合,然后迭代获取锁,然后在迭代后释放锁。为了使任何添加,删除等工作,操作需要由同一个锁保护。
// example only
public class LockingVector {
private final Vector v;
private final ReentrantLock lock = new ReentrantLock();
public void lock(){
lock.lock();
}
public void unlock(){
lock.unlock();
}
// other 'vector' method delegated to v
public Object get() {
return v.get();
}
}
然后使用是做
之类的事情 public class Main {
public static void main(String[] args){
Vector v = ...
LockingVector lv = new LockingVector(v);
try {
lv.lock();
// do stuff here (add, delete, iterate, etc.)
} finally {
lv.unlock();
}
}
}