Я относительно новичок в многопоточности, и я пытаюсь использовать 3 разных потока в игре, которую я создаю. Один поток выполняет обновление задним концом, другой используется для чертежа, а третий - для загрузки и/или генерации новых фрагментов (и вскоре для их сохранения, когда они мне не нужны). У меня были нитки и обновления потоков, работающих очень хорошо, а затем, когда я добавил третий поток в микс, у меня возникли проблемы с ConcurrentModificationExceptions. Они происходят внутри моего... для всех циклов, в которых я перебираю объекты массива ArrayList.
Я попытался заблокировать, когда каждый поток имеет доступ и модифицирует кусок ArrayList с использованием Phaser следующим образом:
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, если они не используют итератор или не требуют его стабильности, и у меня нет причин предотвращать повторение итерации двух потоков через список одновременно, когда ничего не изменяется Это.
Более полные копии соответствующих файлов:
В идеале, вы должны сделать свой код так быстро, что вы можете загружать куски между кадрами. Вы должны иметь возможность проектировать это, чтобы паузы не занимали больше пары миллисекунд, и все по-прежнему работает гладко. Таким образом, ваши пользователи быстро загружают куски, и вам не нужно иметь дело с многопоточным кодом и преследовать условия гонки.
Если окажется, что вам абсолютно необходимо использовать потоки, ограничьте минимальное изменяемое состояние, разделенное между ними. В идеале у вас будет две очереди: одна с запросом на загрузку и одна с загруженными уровнями. Эти две очереди должны быть единственным способом обмена этими потоками. Когда какой-либо объект отправляется в другой поток, нить источника больше не должна его использовать. Таким образом, вы можете избежать условий гонки без добавления синхронизации.
Чтобы более точно ответить на ваш вопрос: ConcurrentModificationException
возникает только при изменении коллекции. Изменение элементов, хранящихся внутри него, не влияет на сам список.
Я очень подозреваю, что у вас что-то не так с вашим кодом синхронизации. Это выглядит бесполезно сложным. В текущей форме только один поток должен получить доступ к chunks
за раз. Другие должны ждать своей очереди. В этом случае Phaser окончательно не нужен. Это задача для простого синхронизированного блока или, в худшем случае, блокировки чтения-записи.
Phaser
- это класс JDK.