我很确定我遗漏了一件小事,但我想不通。我一直在关注所有文档并寻找解决方案,但我仍然遇到此错误。
场景如下: 我有一个显示线程 (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/