2015-09-04 1 views
0

Я пытаюсь исправить ошибку, связанную с ConcurrentModificationException во время итерации Collections.synchronizedMap.Будет ли Collections.synchronizedMap быть потокобезопасным, если синхронизируется во время итерации?

Как запросил Javadoc, процесс итерации был синхронизирован на карте.

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

Помимо изменения во время процесса итерации есть ли другая возможность, которая может вызвать это исключение?

Поскольку итерация уже синхронизирована, из моего понимания, другая нить не сможет вносить изменения, такие как add() или remove(), это правильно?

Я действительно новичок в этих вещах. Любая помощь будет действительно оценена.

Обновление

Спасибо так много для всех о помощи особенно подробные объяснения от @ Marco13. Я сделал небольшой код, чтобы проверить и проверить эту проблему, и код прилагается здесь:

public class TestCME { 
    public static void main(String[] args){ 
     TestMap tm = new TestMap(); 
     for(int i = 0;i < 50;i++){ 
      tm.addCity(i,new City(i * 10)); 
     } 
     RunnableA rA = new RunnableA(tm); 
     new Thread(rA).start(); 
     RunnableB rB = new RunnableB(tm); 
     new Thread(rB).start(); 
    } 
} 
class TestMap{ 
    Map<Integer,City> cityMap; 

    public TestMap(){ 
     cityMap = Collections.synchronizedMap(new HashMap<Integer,City>()); 
    } 

    public Set<Integer> getAllKeys(){ 
     return cityMap.keySet(); 
    } 

    public City getCity(int id){ 
     return cityMap.get(id); 
    } 

    public void addCity(int id,City city){ 
     cityMap.put(id,city); 
    } 

    public void removeCity(int id){ 
     cityMap.remove(id); 
    } 
} 
class City{ 
    int area; 
    public City(int area){ 
     this.area = area; 
    } 
} 
class RunnableA implements Runnable{ 
    TestMap tm; 
    public RunnableA(TestMap tm){ 
     this.tm = tm; 
    } 
    public void run(){ 
     System.out.println("Thread A is starting to run......"); 

     if(tm != null && tm.cityMap != null && tm.cityMap.size() > 0){ 

      synchronized (tm.cityMap){ 
       Set<Integer> idSet = tm.getAllKeys(); 
       Iterator<Integer> itr = idSet.iterator(); 
       while(itr.hasNext()){ 
        System.out.println("Entering while loop....."); 
        Integer id = itr.next(); 
        System.out.println(tm.getCity(id).area); 
        try{ 
         Thread.sleep(100); 
        }catch(Exception e){ 
         e.printStackTrace(); 
        } 
       } 
      } 

      /*Set<Integer> idSet = tm.getAllKeys(); 
      Iterator<Integer> itr = idSet.iterator(); 
      while(itr.hasNext()){ 
       System.out.println("Entering while loop....."); 
       Integer id = itr.next(); 
       System.out.println(tm.getCity(id).area); 
       try{ 
        Thread.sleep(100); 
       }catch(Exception e){ 
        e.printStackTrace(); 
       } 
      }*/ 

     } 
    } 
} 
class RunnableB implements Runnable{ 
    TestMap tm; 
    public RunnableB(TestMap tm){ 
     this.tm = tm; 
    } 
    public void run(){ 
     System.out.println("Thread B is starting to run......"); 
     System.out.println("Trying to add elements to map...."); 
     tm.addCity(50,new City(500)); 
     System.out.println("Trying to remove elements from map...."); 
     tm.removeCity(1); 
    } 
} 

Я пытался восстановить свою ошибку, так что код является немного длинным, и я извиняюсь за это. В потоке А я делаю итерацию на карте, в то время как в потоке B я пытаюсь добавить и удалить элементы из карты. При правильной синхронизации на карте (как сообщается @ Marco13) я не увижу ConcurrentModificationException, если без синхронизации или синхронизации на объекте TestMap появляется исключение. Наверное, я сейчас понимаю эту проблему. Любое двойное подтверждение или предложение по этому поводу очень приветствуется. Еще раз спасибо.

+2

Предлагаю вам разместить автономный, воспроизводимый пример и соответствующую трассировку стека, иначе вам будет сложно помочь, и ваш вопрос может быть закрыт. – Mena

+0

Если вы держите замок, есть только два способа: как карта может быть изменена, 1. потоком, которому принадлежит блокировка, 2. через ссылку на исходную карту, которая была обернута 'Collections.synchronizedMap' , Чтобы избежать второго варианта, вы не должны ссылаться на завернутую карту. Что касается первого, поможет только тщательный анализ кода. – Holger

+0

Большое спасибо за комментарии. Я добавил пример кода, который генерирует исключение. Сейчас я делаю анализ кода. По второй причине вы могли бы дать мне более подробную информацию или документы? Я не очень понимаю обернутую карту. Большое спасибо. – alice

ответ

4

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

EDIT Смотрите также обновление в нижней части ответа

Вы должны быть в курсе, что вы на самом деле синхронизации на. При создании карты с

Map<Key, Value> synchronizedMap = 
    Collections.synchronizedMap(map); 

то синхронизация будет происходить на synchronizedMap. Это означает, что следующий потокобезопасно:

void executedInFirstThread() 
{ 
    synchronizedMap.put(someKey, someValue); 
} 
void executedInSecondThread() 
{ 
    synchronizedMap.remove(someOtherKey); 
} 

Хотя оба метод может быть exectued одновременно разными потоками, это поточно: Когда первый поток выполняет метод put, то второй поток будет до wait перед выполнением метода remove. Метод put и remove будет выполнять , а не. Для этого и предназначена синхронизация.


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

Поэтому, необходимо синхронизировать все итерации, а не только одиночные методы:

void executedInFirstThread() 
{ 
    synchronized (synchronizedMap) 
    { 
     for (Key key : synchronizedMap.keySet()) 
     { 
      System.out.println(synchronizedMap.get(key)); 
     } 
    } 
} 
void executedInSecondThread() 
{ 
    synchronizedMap.put(someKey, someValue); 
} 

Без synchronized блока, первый поток может частично итерация по всей карте, а затем, в в середине итерации второй поток мог вызвать put на карте (таким образом, выполняя одновременную модификацию ). Когда первый поток затем хотел продолжить итерацию, будет выброшен ConcurrentModificationException.

С synchronized блока, это не может произойти: Когда первый поток входит в этот synchronized блок, то второй поток будет ждать, прежде чем он может позвонить put. (Он должен будет дождаться, пока первая нить не покинет блок synchronized, после чего он может безопасно выполнить модификацию).


Тем не менее, отметим, что вся эта концепция всегда относится к объект, который синхронизируется на.

В вашем случае, вы имели класс, подобный следующему:

class TestMap 
{ 
    Map<Key, Value> synchronizedMap = 
     Collections.synchronizedMap(new HashMap<Key, Value>()); 

    public Set<Key> getAllKeys() 
    { 
     return synchronizedMap.keySet(); 
    } 
} 

Затем вы использовали его, как это:

TestMap testMap = new TestMap(); 
    synchronized(testMap) 
    { 
     Set<Key> keys = testMap.getAllKeys(); 
     for (Key key : keys) 
     { 
      ... 
     } 
    } 
} 

В этом случае вы синхронизируете на testMap объекте , и не на sychronizedMap объект, который он содержит. Таким образом, для итерации синхронизации не происходит. Один поток может выполнять итерацию (синхронизируется на объекте testMap). В то же время другой поток мог (одновременно) изменить synchronizedMap, что приведет к ConcurrentModificationException.


Решение зависит от общей структуры. Одним из возможных решений было бы подвергать synchronizedMap ...

class TestMap 
{ 
    Map<Key, Value> synchronizedMap = 
     Collections.synchronizedMap(new HashMap<Key, Value>()); 

    ... 

    public Object getSynchronizationMonitor() 
    { 
     return synchronizedMap; 
    } 
} 

и использовать его для синхронизации итерации ...

TestMap testMap = new TestMap(); 
synchronized(testMap.getSynchronizationMonitor()) 
{ 
    ... 
} 

... но это не общая рекомендация , но предназначен только для передачи идеи.: Вы должны знать, на каком объекте вы должны синхронизироваться. Для реального приложения есть, скорее всего, более элегантные решения.

Update:

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

+0

Большое вам спасибо за подробную помощь. Наверное, я наконец понял это сейчас. Некоторый код был добавлен мной. Еще раз спасибо. – alice

+0

@AliceTan Я добавил небольшое обновление, а также – Marco13

+0

Спасибо за предложение. Это то, о чем я беспокоюсь. – alice

Смежные вопросы