ConcurrentModificationException, несмотря на использование синхронизированного
public synchronized X getAnotherX(){
if(iterator.hasNext()){
X b = iterator.next();
String name = b.getInputFileName();
...
return b;
}
else{return null;}
}
несмотря на синхронизированный оператор в заголовке объявления, я все равно получаю исключение ConcurrentModificationException в строке, где я использую iterator.next(); Что здесь не так?
Ответы
Ответ 1
ConcurrentModificationException
обычно не имеет ничего общего с несколькими потоками. В большинстве случаев это происходит из-за того, что вы изменяете коллекцию, в которой она итерируется внутри тела цикла итерации. Например, это приведет к этому:
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
Item item = (Item) iterator.next();
if (item.satisfiesCondition()) {
collection.remove(item);
}
}
В этом случае вы должны использовать метод iterator.remove()
. Это происходит одинаково, если вы добавляете в коллекцию, и в этом случае нет общего решения. Тем не менее, подтип ListIterator
может использоваться при работе со списком и имеет метод add()
.
Ответ 2
Я согласен с вышеприведенными утверждениями о ConcurrentModificationException
, которые часто происходят в результате изменения коллекции в том же потоке, что и итерация. Однако это не всегда причина.
Вещь, о которой нужно помнить о synchronized
, заключается в том, что она гарантирует только эксклюзивный доступ, если также синхронизируются все обращения к общему ресурсу.
Например, вы можете синхронизировать доступ к общей переменной:
synchronized (foo) {
foo.setBar();
}
И вы можете думать, что у вас есть эксклюзивный доступ к нему. Тем не менее, нет ничего, чтобы остановить другой поток, просто делая что-то без блока synchronized
:
foo.setBar(); // No synchronization first.
Через неудачу (или Murphy Law, "Все, что может пойти не так, пойдет не так".), эти два потока могут произойти для выполнения в одно и то же время. В случае структурных модификаций некоторых широко используемых коллекций (например, ArrayList
, HashSet
, HashMap
и т.д.) Это может привести к ConcurrentModificationException
.
Трудно полностью предотвратить проблему:
-
Вы можете документировать требования синхронизации, например. вставка "вы должны синхронизировать на blah
перед изменением этой коллекции" или "сначала закрепить bloo
блокировку", но полагаясь на то, что пользователи обнаруживают, читают, понимают и применяют инструкцию.
Существует аннотация javax.annotation.concurrent.GuardedBy
, которая может помочь документировать это стандартным образом; проблема в том, что вы должны иметь некоторые средства для проверки правильного использования аннотации в toolchain. Например, вы могли бы использовать что-то вроде Google errorprone, который может проверять в некоторых ситуациях, но он не идеален.
-
Для простых операций над коллекциями вы можете использовать методы Collections.synchronizedXXX
factory, которые обертывают коллекцию так что каждый вызов метода сначала синхронизируется в базовой коллекции, например метод SynchronizedCollection.add
:
@Override public boolean add(E e) {
synchronized (mutex) { return c.add(obj); }
}
Где mutex
- это синхронизированный экземпляр (часто сам SynchronizedCollection
), а c
- обернутая коллекция.
Два оговорки с таким подходом:
-
Вы должны быть осторожны, чтобы обернутая коллекция не могла быть доступна каким-либо другим способом, поскольку это позволило бы несинхронизированный доступ, исходную проблему. Обычно это достигается путем немедленной упаковки коллекции при построении:
Collections.synchronizedList(new ArrayList<T>());
-
Синхронизация применяется к вызову метода, поэтому, если вы выполняете некоторую сложную операцию, например
if (c.size() > 5) { c.add(new Frob()); }
тогда у вас нет эксклюзивного доступа во всей этой операции, только для вызовов size()
и add(...)
индивидуально.
Чтобы получить взаимоисключающий доступ в течение продолжительности сложной операции, вам необходимо выполнить внешнюю синхронизацию, например. synchronized (c) { ... }
. Это требует, чтобы вы знали правильную вещь для синхронизации, однако, которая может быть или не быть c
.