ConcurrentModificationException GL2 /更新线程,包含同步和迭代器

时间:2014-01-13 16:14:01

标签: java concurrency arraylist jogl

我很确定我错过了一件小事,但我无法弄清楚这一点。我正在关注所有文档,并寻找解决方案,但我仍然遇到此错误。

现场如下: 我有一个显示线程(openGL)和逻辑更新线程。两个线程都通过arraylist进行迭代,该arraylist包含子节点。这些子节点可以添加其他子节点。如果您熟悉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;
            }
        }
    }
}

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

任何帮助都会受到赞赏和欢迎,因为对我来说,有点难以理解为什么以前的开发人员会这样做。

1 个答案:

答案 0 :(得分:1)

<强>问题

如果我没记错 - 这里的问题是,你有两个同步的块

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

两个块仅为自己锁定。从DB-Recources和DB-Transactions中可能知道它的锁定不是List。

因此,您可以同时调用两个不同的同步块。如果在同一个Object上执行此操作,则最终会在同一个Object上进行并发调用。

例如:

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。自Java 1.5以来,Lock-Interface是新的,是现代同步的方式。

以下显示了一个拥有缓冲区的类,该缓冲区将由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()启动test-Threads:

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