Итерирует ли ConcurrentHashMap значение streamобезопасности?

В javadoc для ConcurrentHashMap следующее:

Операции поиска (включая get) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая put и remove). Retrievals отражают результаты последних завершенных операций по обновлению с их началом. Для совокупных операций, таких как putAll и clear, одновременное извлечение может отражать вставку или удаление только некоторых записей. Аналогично, iteratorы и enums возвращают элементы, отражающие состояние хеш-таблицы в какой-то момент времени или после создания iteratorа / enums. Они не выбрасывают ConcurrentModificationException. Однако iteratorы предназначены для использования только по одному streamу за раз.

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

Что это значит?

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

Что произойдет, если я попытаюсь выполнить итерацию карты двумя streamами одновременно?

Он будет работать, как ожидалось, если каждый из streamов использует свой собственный iterator.

Что произойдет, если я поместил или удалю значение с карты при его повторении?

Гарантируется, что все это не сломается, если вы это сделаете (это часть того, что означает «параллельный» в ConcurrentHashMap ). Однако нет никакой гарантии, что один stream увидит изменения на карте, которые выполняет другой stream (без получения нового iteratorа с карты). Итератор гарантированно отражает состояние карты во время ее создания. Дальнейшие изменения могут быть отражены в iteratorе, но они не обязательно должны быть.

В заключение, заявление, подобное

 for (Object o : someConcurrentHashMap.entrySet()) { // ... } 

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

Вы можете использовать этот class для проверки двух streamов доступа и одного мутирования общего экземпляра ConcurrentHashMap :

 import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Map map; public Accessor(Map map) { this.map = map; } @Override public void run() { for (Map.Entry entry : this.map.entrySet()) { System.out.println( Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']' ); } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); System.out.println(Thread.currentThread().getName() + ": " + i); } } } private void run() { Accessor a1 = new Accessor(this.map); Accessor a2 = new Accessor(this.map); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Никакое исключение не будет выбрано.

Совместное использование одного и того же iteratorа между streamами доступа может привести к взаимоблокировке:

 import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while(iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Как только вы начнете использовать один и тот же Iterator> среди streamов accessor и mutator, появится java.lang.IllegalStateException s.

 import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Random random = new Random(); private final Iterator> iterator; private final Map map; public Mutator(Map map, Iterator> iterator) { this.map = map; this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { try { iterator.remove(); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } catch (Exception ex) { ex.printStackTrace(); } } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(map, this.iterator); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

Это означает, что вы не должны делиться объектом iteratorа между несколькими streamами. Создание нескольких iteratorов и одновременное использование их в отдельных streamах прекрасное.

Это может дать вам хорошее представление

ConcurrentHashMap обеспечивает более высокий уровень параллелизма, слегка ослабляя обещания, которые он дает абонентам. Операция поиска вернет значение, вставленное последней выполненной операцией вставки, а также может вернуть значение, добавленное операцией вставки, которая выполняется параллельно (но ни в коем случае не вернет результат бессмысленности). Итераторы, возвращаемые ConcurrentHashMap.iterator (), будут возвращать каждый элемент один раз максимум и никогда не будут бросать ConcurrentModificationException, но могут или не могут отражать вставки или удаления, которые произошли с момента создания iteratorа . Для обеспечения безопасности streamа при повторной сборке не требуется (или даже возможно) столовая блокировка. ConcurrentHashMap может использоваться как замена для synchronizedMap или Hashtable в любом приложении, которое не полагается на возможность блокировки всей таблицы для предотвращения обновлений.

В соответствии с этим:

Однако iteratorы предназначены для использования только по одному streamу за раз.

Это означает, что при использовании iteratorов, созданных ConcurrentHashMap в двух streamах, они безопасны, это может привести к неожиданному результату в приложении.

Что это значит?

Это означает, что вы не должны пытаться использовать один и тот же iterator в двух streamах. Если у вас есть два streamа, которым необходимо перебирать ключи, значения или записи, то каждый из них должен создавать и использовать свои собственные iteratorы.

Что произойдет, если я попытаюсь выполнить итерацию карты двумя streamами одновременно?

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

Но если два streamа используют разные iteratorы, вы должны быть в порядке.

Что произойдет, если я поместил или удалю значение с карты при его повторении?

Это отдельная проблема, но секция javadoc, которую вы цитируете, адекватно отвечает на нее. В принципе, iteratorы являются streamобезопасными, но не определяется , будете ли вы видеть эффекты любых одновременных вставок, обновлений или исключений, отраженных в последовательности объектов, возвращаемых iteratorом. На практике это, вероятно, зависит от того, где на карте происходят обновления.

  • Является ли stl-вектор одновременным чтением streamобезопасным?
  • Как мне вызвать некоторый метод блокировки с тайм-аутом в Java?
  • Кажется, что сервлет обрабатывает несколько одновременных запросов браузера синхронно
  • Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет?
  • Atomic UPDATE .. SELECT в Postgres
  • Неправильная публикация ссылки на объект Java
  • Невозможно создать кэшированный пул streamов с ограничением размера?
  • Зачем использовать ReentrantLock, если вы можете использовать синхронизированный (это)?
  • Существует ли ExecutorService, который использует текущий stream?
  • Контейнер Jboss Java EE и ExecutorService
  • Какой алгоритм параллельной сортировки имеет лучшую среднюю производительность?
  • Давайте будем гением компьютера.