Почему этот код генерирует исключение javu ConcurrentModificationException? - PullRequest
3 голосов
/ 05 мая 2011
public final class ClientGateway {

   private static ClientGateway instance;
   private static List<NetworkClientListener> listeners = Collections.synchronizedList(new ArrayList<NetworkClientListener>());
   private static final Object listenersMutex = new Object();
   protected EventHandler eventHandler;


   private ClientGateway() {
      eventHandler = new EventHandler();
   }

   public static synchronized ClientGateway getInstance() {
      if (instance == null)
         instance = new ClientGateway();
      return instance;
   }

   public void addNetworkListener(NetworkClientListener listener) {
     synchronized (listenersMutex) {
        listeners.add(listener);
     }
   }


   class EventHandler {

     public void onLogin(final boolean isAdviceGiver) {
        new Thread() {
           public void run() {
              synchronized (listenersMutex) {
                 for (NetworkClientListener nl : listeners) 
                    nl.onLogin(isAdviceGiver);
              }
           }
        }.start();
     }

   }
}

Этот код генерирует исключение ConcurrentModificationException. Но я подумал, что если они оба синхронизируются на listenersMutex, то они должны выполняться последовательно?Весь код в функциях, которые работают со списком слушателей, работает в синхронизированных блоках, которые синхронизируются в Mutex.Единственный код, который изменяет список, это addNetworkListener (...) и removeNetworkListener (...), но в данный момент никогда не вызывается removeNetworkListener.

То, что происходит с ошибкой, заключается в том, что NetworkClientListener по-прежнемудобавляется, пока функция / поток onLogin выполняет итерацию слушателей.

Спасибо за понимание!

EDIT: NetworkClientListener является интерфейсом и оставляет реализацию "onLogin""до кодера, реализующего функцию, но их реализация функции не имеет доступа к списку слушателей.

Кроме того, я просто полностью перепроверил, и нет никакого изменения списка вне addNetworkListener ()и функции removeNetworkListener (), остальные функции только повторяют список.Изменение кода с:

for (NetworkClientListener nl : listeners) 
   nl.onLogin(isAdviceGiver);

На:

for(int i = 0; i < listeners.size(); i++)
   nl.onLogin(isAdviceGiver);

Появляется для решения проблемы параллелизма, , но я уже знал это и хотел бы знать, что вызывает его впервое место.

Еще раз спасибо за вашу постоянную помощь!

Исключение: Исключение в потоке "Thread-5" java.util.ConcurrentModificationException в java.util.ArrayList $ Itr.checkForComodification $(ArrayList.java:782) на java.util.ArrayList $ Itr.next (ArrayList.java:754) на chapchat.client.networkcommunication.ClientGateway $ EventHandler $ 5.run (ClientGateway.java:283)

РЕДАКТИРОВАТЬ Хорошо, я чувствую себя немного глупым.Но спасибо за вашу помощь!В частности, MJB & jprete!

Ответ: Кто-то реализовал onLogin (), добавив новый слушатель к шлюзу.Поэтому (поскольку синхронизация java основана на потоках и является реентерабельной, поэтому поток может не блокироваться сам по себе), когда мы вызывали onLogin () в его реализации, мы перебирали слушателей и в процессе этого добавлялиновый слушатель.

Решение: предложение MJB использовать CopyOnWriteArrayList вместо синхронизированных списков

Ответы [ 3 ]

3 голосов
/ 05 мая 2011

Мьютексы защищают только от доступа из нескольких потоков.Если у nl.onLogin() есть логика, которая добавляет слушателя в список listeners, то может быть выброшено ConcurrentModificationException, потому что к нему обращаются (итератор) и изменяют (добавление) одновременно.

РЕДАКТИРОВАТЬ: Возможно, поможет некоторая дополнительная информация.Насколько я помню, коллекции Java проверяют наличие одновременных изменений, сохраняя счетчик изменений для каждой коллекции.Каждый раз, когда вы выполняете операцию, которая изменяет коллекцию, счетчик увеличивается.Чтобы проверить целостность операций, счетчик проверяется в начале и в конце операции;если счетчик изменился, то коллекция выдает ConcurrentModificationException в точке доступа , а не в точке изменения.Для итераторов он проверяет счетчик после каждого вызова next(), поэтому при следующей итерации цикла по listeners вы должны увидеть исключение.

2 голосов
/ 05 мая 2011

Я должен признать, что тоже не вижу - если действительно не вызывается removeListeners.

Какова логика бита nl.onLogin? Если бы это изменило материал, это могло вызвать исключение.

Совет между прочим, если вы ожидаете, что прослушиватели будут добавляться довольно редко, вы можете создать список типа CopyOnWriteArrayList - в этом случае ваши мьютексы вообще не нужны - CopyOnWriteArrayList полностью поточнобезопасен и возвращает слабо последовательный итератор, который никогда не будет выбрасывать CME (кроме случаев, где я только что сказал, в nl.onLogin).

1 голос
/ 05 мая 2011

Вместо ArrayList использование может использовать потокобезопасный класс CopyOnWriteArrayList, который не генерирует исключение ConcurrentModificationException, даже если он был изменен во время итерации.При выполнении итерации, если он пытается изменить (добавить, обновить), он создает копию списка, но итератор продолжит работу с исходным.

Это немного медленнее, чем ArrayList.Это полезно в тех случаях, когда вы не хотите синхронизировать итерации.

...