Как предотвратить вложенные синхронизированные блоки при итерации по коллекции - PullRequest
6 голосов
/ 26 февраля 2011

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

Однако вложенные синхронизированные блоки не рекомендуются, поскольку они могут привести к взаимоблокировке. Как бы я решил эту проблему?

Collection<Data> dataCollection = something.getDataCollection();

synchronized ( dataCollection ) {
  for ( final Data data : dataCollection ) {
    synchronized ( data ) {
      data.doSomething();  // doSomething() changes object state
    }
  }
}

Ответы [ 6 ]

5 голосов
/ 26 февраля 2011

Вы можете взять копию коллекции и заблокировать только один объект за раз.

Collection<Data> dataCollection = something.getDataCollection();
Collection<Data> copy;
synchronized ( dataCollection ) {
  copy = new ArrayList<Data>(dataCollection);
}

for (Data data : copy) {
    synchronized ( data ) {
      data.doSomething();  // doSomething() changes object state
    }
}
5 голосов
/ 26 февраля 2011

Я думаю, что вы можете использовать CopyOnWriteArrayList вместо внешней синхронизации.

Потокобезопасный вариант ArrayList, в котором все мутативные операции (сложение, установка и т. Д.) Реализуются с помощьюсделать свежую копию базового массива.Обычно это слишком дорого, но может быть более эффективным, чем альтернативы, когда операции обхода значительно превосходят число мутаций, и полезно, когда вы не можете или не хотите синхронизировать обходы, но при этом необходимо исключить помехи между параллельными потоками

4 голосов
/ 27 февраля 2011

Не могу поверить, что никто не указал, что первый способ избежать синхронизации в объекте Data - это сам по себе потокобезопасный!Это также правильный способ обработки синхронизации - если вы знаете, что ваш объект будет доступен для нескольких потоков, обрабатывайте синхронизацию так, как вы считаете нужным внутри класса, а не в коде, который может получить к нему доступ.Вы также наверняка будете более эффективными, потому что вы можете ограничить синхронизацию только критическими блоками, использовать ReadWriteLock, jucatomic и т. Д.

3 голосов
/ 26 февраля 2011

Вложенная синхронизация может привести к тупику, но это не обязательно. Один из способов избежать взаимоблокировок - определить порядок синхронизации объектов и всегда следовать ему.

Если вы всегда синхронизируете объект dataCollection до того, как синхронизируете объекты данных, вы не заблокируетесь.

1 голос
/ 26 февраля 2011

Используете ли вы CopyOnWriteArrayList, как сказал Божо, или скопируйте список перед итерацией, как говорит Питер, должно зависеть от того, насколько вы ожидаете, что список будет отредактирован по сравнению с итерацией по.итерируется гораздо больше, чем модифицируется.

Используйте копирование списка, если вы думаете, что он будет изменен гораздо больше, чем итерация.

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

Удачи!

1 голос
/ 26 февраля 2011

Взгляните на ReentrantReadWriteLock .С помощью этого класса вы можете реализовать блокировку, которая позволяет любому количеству неизменяющих (читающих) потоков одновременно получать доступ к общему свойству, но только один изменяющий (записывающий) поток для одновременного доступа к нему (все другие читатели и писатели).блокируются, пока поток записи не снимет блокировку записи).Не забудьте тщательно протестировать свою реализацию, поскольку неправильное использование блокировок все еще может привести к состоянию гонки и / или тупикам.

...