我对多线程技术还比较陌生,我正尝试在自己创建的游戏中使用3个不同的线程。一个线程正在执行后端更新,另一个线程正在用于图形绘制,第三个线程用于加载和/或生成新的块(不久,当我不需要它们时,将其保存下来)。我的绘制和更新线程工作得很好,然后当我将第三个线程添加到混合中时,我开始遇到ConcurrentModificationExceptions问题。它们发生在我的for ... all循环中,其中我遍历了块对象的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
最佳答案
理想情况下,您应该使代码运行得如此之快,以便可以在帧之间加载块。您应该能够对此进行设计,以使暂停所花费的时间不会超过几毫秒,并且一切仍然可以顺利进行。这样,您的用户即可快速加载块,而您不必处理多线程代码和追逐竞争条件。
如果事实证明您绝对必须使用线程,则将它们之间共享的最小可变状态限制为最小。理想情况下,您将有两个队列,一个队列具有加载请求,一个队列具有已加载的级别。这两个队列应该是那些线程通信的唯一方式。一旦某个对象被发送到另一个线程,原始线程将不再以任何方式使用它。这样,您可以避免争用情况而无需添加同步。
为了更直接地回答您的问题:ConcurrentModificationException
仅在您修改集合时才会出现。修改存储在其中的元素不会影响列表本身。
我高度怀疑您的同步代码有问题。它看起来不必要地复杂。在当前形式下,一次只能有一个线程访问chunks
。其他人必须等待轮到他们。在这种情况下,绝对没有必要使用Phaser。对于简单的同步块,或者在最坏的情况下,读写锁定,这是一项工作。