什么算作修改?

时间:2015-01-24 20:35:44

标签: java multithreading arraylist concurrentmodification

我对多线程比较陌生,我试图在我正在创建的游戏中使用3个不同的线程。一个线程正在执行后端更新,另一个线程正在用于绘图,第三个是加载和/或生成新块(并且很快就会在我不需要时将它们保存下来)。我有绘制和更新线程工作正常,然后当我添加第三个线程混合,我开始遇到ConcurrentModificationExceptions问题。它们发生在我的所有循环中,我循环遍历块对象的ArrayList。

我试图锁定每个线程能够使用Phaser访问和修改块ArrayList,如下所示:

private volatile ArrayList<Chunk> chunks = new ArrayList<Chunk>();
private volatile int chunksStability = 0; //+'ive = # threads accessing, -'ive = # threads editing
private volatile Object chunkStabilityCountLock = new Object();
private volatile Phaser chunkStabilityPhaser = new Phaser() {
    protected boolean onAdvance(int phase, int registeredParties) {
        synchronized(chunkStabilityCountLock)
        {
            if (registeredParties == 0)
            {
                chunksStability = 0;
            }
            else
            {
                chunksStability = Math.max(Math.min(chunksStability*-1, 1), -1);
            }
        }
        return false;
    }
};

//...

/**
 *  Prevents other threads from editing <b>World.chunks</b>. 
 *  Calling this will freeze the thread if another thread has called <b>World.destabalizeChunks()</b>
 *  without calling <b>World.stabalizeChunks()</b>
 */
public void lockEditChunks()
{
    chunkStabilityPhaser.register();
    if (this.chunkStabilityPhaser.getUnarrivedParties() > 1 && this.chunksStability < 0) //number threads currently editing > 0
    {
        this.chunkStabilityPhaser.arriveAndAwaitAdvance(); //wait until threads editing finish
    }

    synchronized(chunkStabilityCountLock)
    {
        ++this.chunksStability;
    }
}
public void unlockEditChunks()
{
    chunkStabilityPhaser.arriveAndDeregister();
}

/**
 *  Prevents other threads requiring stability of <b>World.chunks</b> from continuing 
 *  Calling this will freeze the thread if another thread has called <b>World.lockEditChunks()</b>
 *  without calling <b>World.unlockEditChunks()</b>
 */
public void destabalizeChunks()
{
    chunkStabilityPhaser.register();
    if (this.chunkStabilityPhaser.getUnarrivedParties() > 1 && this.chunksStability > 0) //number threads currently editing > 0
    {
        this.chunkStabilityPhaser.arriveAndAwaitAdvance(); //wait until threads editing finish
    }

    synchronized(chunkStabilityCountLock)
    {
        --this.chunksStability;
    }
}
public void stabalizeChunks()
{
    chunkStabilityPhaser.arriveAndDeregister();
}

然而,我仍然没有取得任何成功。我想知道我是否得到并发修改异常的原因可能与我可能正在修改实际的Chunk对象有关。这会被视为修改,并导致ConcurrentModificationException。我知道我没有在同一个线程中执行修改,因为不会一直抛出异常。让我相信错误只发生在一个线程(我不知道哪个)到达其执行中的特定点而另一个线程迭代通过块ArrayList时。

我知道简单的解决方案是停止使用for ... all循环,而是手动执行循环,如下所示:

for (int i = 0; i < chunks.size(); ++i)
{
    Chunk c = chunks.get(i);
}

但是,我担心这会导致在块状对象在arraylist中移动时屏幕上偶尔会出现抽搐行为。我不想完全跨所有线程同步对它的访问,因为这会妨碍性能,这可能会成为一个相当大的项目,需要尽可能提高效率。另外,我没有任何理由阻止2个线程修改Chunk ArrayList,如果他们不使用迭代器或要求它的稳定性,我也没有任何理由阻止2个线程迭代什么都没有修改它同时通过列表。

相关文件的更完整副本:

World.java
Chunk.java

WorldBuilder.java
ChunkLoader.java

1 个答案:

答案 0 :(得分:1)

理想情况下,您应该使代码如此快速,以便可以在帧之间加载块。你应该能够设计这个,所以暂停不会超过几毫秒,一切仍然顺利。这样您的用户就可以快速加载块,而且您不必处理多线程代码并追逐竞争条件。

如果事实证明你绝对必须使用线程,则限制它们之间共享的最小可变状态。理想情况下,您将有两个队列,一个具有加载请求,另一个具有加载级别。这两个队列应该是这些线程通信的唯一方式。一旦某个对象被发送到另一个线程,原始线程就不应再以任何方式使用它。这样,您可以在不添加同步的情况下避免竞争条件。


更直接地回答您的问题:ConcurrentModificationException仅在您修改集合时才会出现。修改存储在其中的元素不会影响列表本身。

我非常怀疑您的同步代码有问题。它看起来不必要地复杂化。在当前形式中,一次只能有一个线程访问chunks。其他人必须等待轮到他们。在这种情况下,Phaser绝对没有必要。这是简单的同步块的工作,或者在最坏的情况下,是读写锁定。