java - ConcurrentModificationException GL2/更新线程,带同步和迭代器

标签 java concurrency arraylist jogl

我很确定我遗漏了一件小事,但我想不通。我一直在关注所有文档并寻找解决方案,但我仍然遇到此错误。

场景如下: 我有一个显示线程 (openGL) 和逻辑更新线程。两个线程都通过一个包含子节点的数组列表进行迭代。这些子节点可以添加其他子节点。如果您熟悉 cocos2d 系列和它的场景图,它正试图成为它的副本。

在 CNode 类之上的声明:

private List<CNode> m_children;

分配和创建

this.m_children = Collections.synchronizedList(new ArrayList<CNode>());

试着画画

 public void visitdraw(GL2 gl){
    synchronized(this.m_children){
        this.draw(gl);
        Iterator<CNode> it = m_children.iterator();
        while (it.hasNext()){
        it.next().visitdraw(gl);
        }
    }
}

并更新

 public void visitupdate(){
    synchronized(this.m_children){
        this.update();
        Iterator<CNode> it = m_children.iterator();
        while (it.hasNext()){
            it.next().visitupdate();
        }
    }
}

当我想删除一个时会出现问题(即使它没有经过测试,我很确定如果我想添加一个新的节点运行时它会引发相同的异常)

public void removeChild(CNode child){
    synchronized(this.m_children){
        Iterator<CNode> it = this.m_children.iterator();
        while(it.hasNext()){
            if (it.next().equals(child)){
                it.remove();
                break;
            }
        }
    }
}

我完全知道这个系统不是处理任何事情的最佳方式,但我确实被迫使用这个代码。我只是无法弄清楚实际问题出在哪里。

我们将不胜感激和欢迎任何帮助,因为对我来说有点难以理解为什么以前的开发人员会这样做。

最佳答案

问题

如果我没记错的话——这里的问题是,你有两个同步块(synchronized block)

  • 一个用于迭代
  • 第二个用于删除元素。

两个 block 都只为自己锁定。它不是锁定的列表,就像您可能从 DB-Recources 和 DB-Transactions 中了解到的那样。

所以可以同时调用两个不同的synchronized block 。如果您在同一个对象上执行此操作,您最终会在同一个对象上进行并发调用。

例如:

private List list 

public void synchronized read() {
   ...iterate over list
}

public void synchronized remove() {
   ... remove some elements in list
}

你有两个线程 A 和 B

那么A和B不能同时调用read(),A和B不能同时调用remove。但他们可以同时调用 read() 和 remove()。

解决方案

尝试使用 java.util.concurrent.locks.ReentrantReadWriteLock 而不是关键字 synchronized。 Lock-Interface 是自 Java 1.5 以来新增的,是进行同步的现代方式。

下面显示了一个拥有缓冲区的类,该缓冲区将由 3 个线程并行读取和更改:

class Container {

    private List<String> buffer = Collections.synchronizedList(new ArrayList<String>());
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    private Lock writeLock = lock.writeLock();
    private Lock readLock = lock.readLock();

    public void readBuffer() {
        readLock.lock();

        Iterator<String> it = buffer.iterator();
        while(it.hasNext()) {
            it.next();
        }

        readLock.unlock();
    }

    public void addOne() {
        writeLock.lock();

        buffer.add("next");

        writeLock.unlock();
    }

    public void removeOne() {
        writeLock.lock();

        if (buffer.size() > 0) {
            buffer.remove(0);
        }

        writeLock.unlock();
    }
}

为了进行测试,您可以移除 readLock。这将导致 ConcurrentModificationException。 以下 main() 启动测试线程:

public class Concurrent {

    public static Container container = new Container();


    public static void main(String[] args) {
        new Thread(new Filler()).start();
        new Thread(new Killer()).start();
        new Thread(new Reader()).start();
    }

    static class Filler implements Runnable {
        @Override
        public void run() {
            while(true) {
                container.addOne();
            }
        }
    }

    static class Killer implements Runnable {
        @Override
        public void run() {
            while(true) {
                container.removeOne();
            }
        }
    }

    static class Reader implements Runnable {
        @Override
        public void run() {
            while(true) {
                container.readBuffer();
            }
        }
    }
}

重点是,您在两种方法中使用相同的 LockObject writeLock。此外,这种锁允许它并行读取数据。如果需要一次只允许一个线程读取数据,您可以使用 ReentrantLock 而不是 ReentrantReadWriteLock

关于java - ConcurrentModificationException GL2/更新线程,带同步和迭代器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21095997/

相关文章:

arraylist - 如何在Kotlin的类外部定义静态init block ?

java - 频谱图的频率范围

java - ARCore : should I add "uses-feature android:glEsVersion="0x0002000 0""?

java - 在 Java 8 中使用流反转 map

Java 并发 : Changes made by task submitted to executor visible after future. get()

java - Java中引用变量读写的原子性

java - 在此示例代码中怎么可能实例化接口(interface)?

java - 更新 ArrayList<HashMap<String, ?>>

java - 如何在 main() 中访问私有(private)静态实例变量

java - 如何在jsp中显示3个数组列表?