Java - поток KeyListener вызывает одновременное изменение.Решение? - PullRequest
4 голосов
/ 22 ноября 2011

Мой цикл обновления игры постоянно перебирает ArrayList, у меня также есть KeyListener, что при нажатии кнопки добавляет Object к этому ArrayList, что, кажется, вызывает исключение одновременной модификации .

После долгих осмотров я решил, что лучше всего попытаться (я новичок в Java) найти способ объединения ключевых событий в основной поток. Учитывая этот сценарий, как я могу это сделать, или есть способ сделать это, которого я не вижу? Спасибо.

PS: Я мог бы найти более продвинутые способы, но я бы предпочел, чтобы он оставался достаточно однопоточным, а ключевые события передавались в основной поток.

Ответы [ 3 ]

2 голосов
/ 22 ноября 2011

РЕШЕНИЕ 1:

Просто имейте в виду, что во время процесса итерации ваш ключевой слушатель будет заблокирован (это также означает, что ваш итератор работает со старыми данными, как в решении 2).

Если вы используете ArrayList, сделайте следующее:

List list = Collections.synchronizedList(new ArrayList());

И используйте этот list объект везде. Вы должны импортировать

import java.util.Collections;

Я согласен с @msandiford, добавляющим, как повторять ....

while(some_condition) {
do something
  synchronized(list) {
    Iterator i = list.iterator();
    while (i.hasNext())
        do_something_with(i.next());
  }
  do something else
}

Это быстрое решение, если у вас есть внешний цикл, в котором вы выполняете итерацию. Так что вы находитесь вне блока synchronized на некоторое время, чтобы KeyListener мог добавить в список массивов.

РЕШЕНИЕ 2:

Если вы хотите использовать COW, просто имейте в виду, что всякий раз, когда происходит операция добавления / обновления, он создаст копию нижележащей коллекции, и ваш итератор не увидит изменения. Но ключевой слушатель НЕ БУДЕТ заблокирован (но с этого момента будет создана новая копия).

import java.util.concurrent.CopyOnWriteArrayList;


List list = new CopyOnWriteArrayList<your_object_type>();

Для итерации:

while(some_condition) {
  do something
  Iterator i = list.iterator();
  while (i.hasNext())
    do_something_with(i.next());

  do something else
}

РЕШЕНИЕ 3:

Это будет небольшое изменение дизайна. Это похоже на Решение 2, но имеет смысл, только если вы выполняете только операции добавления. Так что вы можете создать еще один временный шаблон List и добавить в этот список Keylistener. И как только ваша итерация завершится, сделайте блок synchronized и переместите все объекты из temp List в List, который вы используете для итерации. Это НЕ БУДЕТ блокировать ваш KeyListener, но итератор увидит старые данные, как в решении 2. Возможно, он будет иметь лучшую производительность по сравнению с решением 2.

Так что выбирайте решение, которое имеет смысл для вашего дизайна.

Ref:

Коллекции Java КПС

2 голосов
/ 22 ноября 2011

Я предполагаю, что вы запускаете цикл обработки событий, в который хотите добавить события в конец тела. Если это так, вы, вероятно, захотите использовать реализацию java.util.Queue, такую ​​как LinkedList, и выполнить

while((event=queue.poll()) != null) { 
  /* process event */ 
  queue.add(keyEvent);
}
1 голос
/ 22 ноября 2011

Для очереди событий я обычно использую ConcurrentLinkedQueue , поскольку очередь очень хорошо подходит для этой модели (намного лучше, чем List / ArrayList).

Очередь намного проще в использовании в многопоточном режиме, как это, потому что нет «итерации» очереди в смысле списка: просто нажмите элемент, захватите элемент.Обе эти операции могут быть атомарными (и относительно простыми для синхронизации).

Я предпочитаю подходить к этой модели с допущением / требованием, что, как только объект будет извлечен из очереди, он может считаться «принадлежащим» этому потоку.(поток, который помещает объект туда, должен оставить объект до тех пор, пока он не получит объект обратно, через другую очередь, если вообще когда-либо).

ConcurrentLinkedQueue:

Неограниченная потокобезопасная очередьна основе связанных узлов.Эта очередь упорядочивает элементы FIFO («первым пришел - первым обслужен») ... ConcurrentLinkedQueue является подходящим выбором, когда многие потоки будут совместно использовать доступ к общей коллекции [но работают «просто отлично» для 2] ...

CopyOnWriteArrayList - это еще одна структура, которую можно использовать, хотя это скорее "общая" структура.Я не уверен, что «быстрее», но, учитывая, что у меня никогда не было проблем с ConcurrentLinkedQueue, это мой первый основной выбор по причинам, указанным выше.Существуют также различные реализации безопасных для параллелизма очередей, в том числе связанные очереди, которые также содержатся в пакете одновременный .

Конечно, для сохранения в "старой школе" просто синхронизируйте для того же объекта при 1) изменении (или итерации) коллекции в EDT (всякий раз, когда это происходит) 2) итерации коллекции в игровом потоке.Это сделает операцию взаимоисключающей и предотвратит это исключение.Один «недостаток» заключается в том, что это может блокировать поток пользовательского интерфейса в зависимости от того, как долго цикл удерживает блокировку.Этот же подход можно использовать и для обычных несинхронизированных очередей.

Счастливое кодирование.

...