Что считается модификацией?

1

Я относительно новичок в многопоточности, и я пытаюсь использовать 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, если они не используют итератор или не требуют его стабильности, и у меня нет причин предотвращать повторение итерации двух потоков через список одновременно, когда ничего не изменяется Это.

Более полные копии соответствующих файлов:

World.java
Chunk.java

WorldBuilder.java
ChunkLoader.java

  • 0
    Вы должны рассмотреть неизменные объекты.
  • 0
    Другой способ, опять же, в зависимости от логики, может заключаться в создании цикла для копии: final Chunk [] copy = chunks.toArray (new Chunk [0]); для (чанк c: копия). Это может быть хорошо в вашем случае, или может быть совершенно неправильно, и вы должны проверить, кто и зачем изменять список
Показать ещё 2 комментария
Теги:
arraylist
multithreading
concurrentmodification

1 ответ

1

В идеале, вы должны сделать свой код так быстро, что вы можете загружать куски между кадрами. Вы должны иметь возможность проектировать это, чтобы паузы не занимали больше пары миллисекунд, и все по-прежнему работает гладко. Таким образом, ваши пользователи быстро загружают куски, и вам не нужно иметь дело с многопоточным кодом и преследовать условия гонки.

Если окажется, что вам абсолютно необходимо использовать потоки, ограничьте минимальное изменяемое состояние, разделенное между ними. В идеале у вас будет две очереди: одна с запросом на загрузку и одна с загруженными уровнями. Эти две очереди должны быть единственным способом обмена этими потоками. Когда какой-либо объект отправляется в другой поток, нить источника больше не должна его использовать. Таким образом, вы можете избежать условий гонки без добавления синхронизации.


Чтобы более точно ответить на ваш вопрос: ConcurrentModificationException возникает только при изменении коллекции. Изменение элементов, хранящихся внутри него, не влияет на сам список.

Я очень подозреваю, что у вас что-то не так с вашим кодом синхронизации. Это выглядит бесполезно сложным. В текущей форме только один поток должен получить доступ к chunks за раз. Другие должны ждать своей очереди. В этом случае Phaser окончательно не нужен. Это задача для простого синхронизированного блока или, в худшем случае, блокировки чтения-записи.

  • 1
    Phaser - это класс JDK.
  • 0
    Я пытался использовать блокировку чтения и записи, но я просто не мог найти чистый способ получить его, чтобы несколько потоков могли одновременно читать или писать, просто не позволяя потокам делать и то, и другое. какие-либо предложения?
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню