В чем разница между ConcurrentHashMap и Collections.synchronizedMap (Map)?


607

У меня есть карта, которая должна быть изменена несколькими потоками одновременно.

Кажется, в Java API есть три разные реализации синхронизированных карт:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

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

Но как насчет двух других? Каковы различия между картами, возвращаемыми Collections.synchronizedMap(Map)и ConcurrentHashMaps? Какой из них подходит к какой ситуации?


7
@SmilesinaJar Ссылка в настоящее время не работает, здесь есть архивная копия этой статьи: почему ConcurrentHashMap лучше, чем Hashtable, и так же хорош, как HashMap
informatik01

2
IBM: Как ConcurrentHashMap предлагает более высокий параллелизм без ущерба для безопасности потоков @ ibm.com/developerworks/java/library/j-jtp08223/...
pramodc84

К вашему сведению, Java 6 представлена ​​в ConcurrentSkipListMapкачестве еще одной поточно-безопасной Mapреализации. Предназначен для одновременной работы под нагрузкой с использованием алгоритма Skip List .
Базилик Бурк

Ответы:


423

Для ваших нужд используйте ConcurrentHashMap. Это позволяет одновременно изменять карту из нескольких потоков без необходимости их блокировки. Collections.synchronizedMap(map)создает блокирующую карту, которая ухудшает производительность, хотя и обеспечивает согласованность (при правильном использовании).

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


8
Глядя на исходный код, синхронизированная карта является лишь реализацией с одним мьютексом (блокировкой), тогда как ConcurrentHashMap более сложен для одновременного доступа
Vinze

123
Также обратите внимание, что ConcurrentHashMap не допускает нулевые ключи или значения. Таким образом, они НЕ являются равными альтернативами синхронизированной карты.
onejigtwojig

24
Я думаю, что вы должны прочитать это http://ria101.wordpress.com/2011/12/12/concurrenthashmap-avoid-a-common-misuse/
Мистер Спарк

5
@AbdullahShaikh Проблема, поднятая в этой статье, была исправлена ​​в Java 7, а в Java 8 были сделаны дальнейшие улучшения.
pulse0ne

5
@hengxin: как только вы выполняете операцию, состоящую из нескольких запросов или обновлений карты, или когда вы выполняете итерации по карте, вы должны вручную синхронизировать карту, чтобы обеспечить согласованность. Синхронизированные карты гарантируют согласованность только для отдельных операций (вызовов методов) на карте, что делает ее более чем бесполезной, так как большинство реальных операций нетривиальны, поэтому вам все равно придется вручную синхронизироваться.
Хольгер

241
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║ Thread-safety ║                   ║                                         ║
║   features    ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

Что касается механизма блокировки: Hashtable блокирует объект , а ConcurrentHashMapблокирует только ведро .


13
Hashtableне является блокирующей частью карты. Посмотрите на реализацию. Он использует synchronizedключ без предоставленной блокировки, так что это в основном означает, что он блокирует целое hashtableв каждой операции.
RMachnik

6
Как насчет синхронизированной карты?
Сэмюэль Эдвин Уорд

3
Поведение Collections.syncronizedMap похоже на базовую карту, за исключением того, что все методы являются поточно-
ориентированными

5
Я бы распечатал таблицу и продал ее по 5 долларов каждая;). Хороший @shevchyk
realPK

Отредактировано: ни один не является полностью потокобезопасным. Это немного вводит в заблуждение новых разработчиков. См. Ibm.com/developerworks/java/library/j-jtp07233/index.html, чтобы понять, что даже ConcurrentHashMap не является полностью поточно-ориентированным для внешних гонок данных. (Например: 1 поток удаляет значение, а другой позже пытается проверить, присутствует ли оно, и поместить его, если нет. Это условие гонки данных и все еще означает, что, несмотря на использование «ConcurrentHashMap», вы не избавлены от всех проблем безопасности потока.
Зомби

142

«Проблемы масштабируемости» Hashtableприсутствуют точно так же, поскольку Collections.synchronizedMap(Map)они используют очень простую синхронизацию, что означает, что только один поток может получить доступ к карте одновременно.

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

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


4
Вот что я хотел! :) Несинхронизированный Итератор - просто чистая сладость! Спасибо за информацию! :) (:
Кунави

Отличный ответ .. но значит ли это, что во время поиска поток не получит последние обновления, так как потоки чтения не синхронизированы.
MrA

@MrA: Вы спрашиваете о ConcurrentHashMap? А что вы подразумеваете под «поиском»?
Майкл Боргвардт

4
@ Майкл Боргвардт для ConcurrentHashmap например. Предположим, есть несколько потоков. некоторые из них обновляют карту, а некоторые получают данные с той же карты. Поэтому в этом сценарии, когда потоки пытаются читать, гарантируется, что они получат последние данные, которые были обновлены, поскольку потоки считывателя не должны удерживать блокировки.
MrA

35

ConcurrentHashMap предпочтительнее, когда вы можете его использовать - хотя для этого требуется как минимум Java 5.

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

Я нашел запись в блоге, которая воспроизводит таблицу из превосходной книги Java Concurrency In Practice , которую я полностью рекомендую.

Collections.synchronizedMap имеет смысл на самом деле, только если вам нужно обернуть карту некоторыми другими характеристиками, возможно, какой-то упорядоченной картой, например TreeMap.


2
Да, кажется, я упоминаю эту книгу в каждом ответе, который я делаю!
Билл Мичелл

Ссылка @BillMichell не работает

@Govinda Отключите JavaScript перед доступом к ссылке. Запись в блоге все еще там!
Билл Мичелл,

32

Основное различие между этими двумя заключается в том, ConcurrentHashMapчто блокируется только часть данных, которые обновляются, в то время как другая часть данных может быть доступна другим потокам. Однако Collections.synchronizedMap()при обновлении все данные будут заблокированы, другие потоки смогут получить доступ к данным только после снятия блокировки. Если есть много операций обновления и относительно небольшое количество операций чтения, вы должны выбрать ConcurrentHashMap.

Также еще одно отличие заключается в том, ConcurrentHashMapчто не будет сохраняться порядок элементов, передаваемых в карте. Это похоже на HashMapсохранение данных. Нет гарантии, что порядок элементов сохранен. Хотя Collections.synchronizedMap()будет сохраняться порядок элементов карты, переданной в. Например, если вы передаете a TreeMapв ConcurrentHashMap, порядок элементов в ConcurrentHashMapможет не совпадать с порядком в TreeMap, но Collections.synchronizedMap()будет сохранять порядок.

Более того, ConcurrentHashMapможет гарантировать, что не будет ConcurrentModificationExceptionвыброшено, пока один поток обновляет карту, а другой поток перебирает итератор, полученный из карты. Однако Collections.synchronizedMap()это не гарантировано.

Есть один пост, который демонстрирует различия этих двух, а также ConcurrentSkipListMap.


13

Синхронизированная карта:

Синхронизированная карта также не сильно отличается от Hashtable и обеспечивает аналогичную производительность в параллельных программах Java. Единственное различие между Hashtable и SynchronizedMap состоит в том, что SynchronizedMap не является устаревшим, и вы можете обернуть любую Карту, чтобы создать ее синхронизированную версию, используя метод Collections.synchronizedMap ().

ConcurrentHashMap:

Класс ConcurrentHashMap предоставляет параллельную версию стандартного HashMap. Это улучшение функциональности synchronizedMap, предоставляемой в классе Collections.

В отличие от Hashtable и Synchronized Map, он никогда не блокирует всю карту, вместо этого он разделяет карту на сегменты, и для них выполняется блокировка. Это работает лучше, если число потоков чтения больше, чем количество потоков записи.

ConcurrentHashMap по умолчанию разделен на 16 регионов и применяются блокировки. Этот номер по умолчанию можно установить при инициализации экземпляра ConcurrentHashMap. При настройке данных в определенном сегменте получается блокировка для этого сегмента. Это означает, что два обновления могут по-прежнему безопасно выполняться одновременно, если каждое из них влияет на отдельные сегменты, что сводит к минимуму конфликт блокировок и, таким образом, увеличивает производительность.

ConcurrentHashMap не генерирует исключение ConcurrentModificationException

ConcurrentHashMap не генерирует исключение ConcurrentModificationException, если один поток пытается изменить его, в то время как другой перебирает его

Разница между synchornizedMap и ConcurrentHashMap

Collections.synchornizedMap (HashMap) вернет коллекцию, которая почти эквивалентна Hashtable, где каждая операция модификации на Map заблокирована на объекте Map, тогда как в случае ConcurrentHashMap безопасность потока достигается путем разделения всей Map на другой раздел на основе уровня параллелизма и только блокировка определенной части вместо блокировки всей карты.

ConcurrentHashMap не допускает нулевые ключи или нулевые значения, в то время как синхронизированный HashMap допускает один нулевые ключи.

Похожие ссылки

Link1

Link2

Сравнение производительности


12
  • Hashtableи ConcurrentHashMapне разрешайте nullключи или nullзначения.

  • Collections.synchronizedMap(Map)синхронизирует все операции ( get, put, sizeи т.д.).

  • ConcurrentHashMap поддерживает полный параллелизм получения и настраивает ожидаемый параллелизм для обновлений.

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


12

В ConcurrentHashMap, блокировка применяется к сегменту вместо всей карты. Каждый сегмент управляет своей собственной внутренней хеш-таблицей. Блокировка применяется только для операций обновления. Collections.synchronizedMap(Map)синхронизирует всю карту.


не могли бы вы взглянуть на stackoverflow.com/questions/48579060/… ?
gstackoverflow

9

Вы правы HashTable, вы можете забыть об этом.

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

synchronized (records) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

Однако не думайте, что ConcurrentHashMapэто простая альтернатива для HashMapтипичного synchronizedблока, как показано выше. Прочитайте эту статью, чтобы лучше понять ее тонкости.


7

Вот несколько:

1) ConcurrentHashMap блокирует только часть Map, но SynchronizedMap блокирует весь MAp.
2) ConcurrentHashMap имеет лучшую производительность по сравнению с SynchronizedMap и более масштабируемый.
3) В случае нескольких читателей и одного писателя ConcurrentHashMap является лучшим выбором.

Этот текст из разницы между ConcurrentHashMap и хеш-таблицей в Java


7

Мы можем добиться безопасности потоков, используя ConcurrentHashMap и synchronizedHashmap и Hashtable. Но есть большая разница, если вы посмотрите на их архитектуру.

  1. synchronizedHashmap и Hashtable

Оба будут поддерживать блокировку на уровне объекта. Поэтому, если вы хотите выполнить какую-либо операцию, такую ​​как put / get, вам нужно сначала получить блокировку. В то же время другим потокам не разрешается выполнять какие-либо операции. Таким образом, только один поток может работать с этим. Так что время ожидания здесь увеличится. Можно сказать, что производительность относительно низкая по сравнению с ConcurrentHashMap.

  1. ConcurrentHashMap

Это будет поддерживать блокировку на уровне сегмента. Он имеет 16 сегментов и поддерживает уровень параллелизма как 16 по умолчанию. Таким образом, одновременно 16 потоков могут работать с ConcurrentHashMap. Более того, операция чтения не требует блокировки. Таким образом, любое количество потоков может выполнить операцию get.

Если thread1 хочет выполнить операцию put в сегменте 2, а thread2 хочет выполнить операцию put в сегменте 4, то здесь это разрешено. Значит, 16 потоков могут одновременно выполнять операцию обновления (вставки / удаления) в ConcurrentHashMap.

Так что время ожидания здесь будет меньше. Следовательно, производительность относительно лучше, чем synchronizedHashmap и Hashtable.


1
, 1. Что произойдет, если несколько потоков пытаются редактировать один и тот же блок? 2. Что произойдет, если, скажем, два потока попытаются прочитать данные из одного и того же блока, где другой поток записывает данные одновременно?
prnjn

6

ConcurrentHashMap

  • Вы должны использовать ConcurrentHashMap, когда вам нужен очень высокий параллелизм в вашем проекте.
  • Это потокобезопасно без синхронизации всей карты.
  • Чтение может происходить очень быстро, в то время как запись выполняется с блокировкой.
  • На уровне объекта нет блокировки.
  • Блокировка на более тонкой детализации на уровне сегмента хэш-карты.
  • ConcurrentHashMap не генерирует исключение ConcurrentModificationException, если один поток пытается изменить его, в то время как другой перебирает его.
  • ConcurrentHashMap использует множество блокировок.

SynchronizedHashMap

  • Синхронизация на уровне объекта.
  • Каждая операция чтения / записи должна получить блокировку.
  • Блокировка всей коллекции - накладные расходы.
  • По сути, это дает доступ только одному потоку ко всей карте и блокирует все остальные потоки.
  • Это может вызвать раздор.
  • SynchronizedHashMap возвращает Iterator, который быстро сбой при одновременной модификации.

источник


4

ConcurrentHashMap оптимизирован для одновременного доступа.

Доступ не блокирует всю карту, но использует более детальную стратегию, которая улучшает масштабируемость. Существуют также функциональные улучшения, специально предназначенные для одновременного доступа, например, параллельные итераторы.


4

Следует обратить внимание на одну важную особенность, отличную ConcurrentHashMapот предоставляемой ею функции параллелизма, а именно отказоустойчивый итератор. Я видел разработчиков, использующих ConcurrentHashMapтолько потому, что они хотят редактировать набор записей - ставить / удалять, перебирая его. Collections.synchronizedMap(Map)не предоставляет отказоустойчивый итератор, но вместо этого он предоставляет отказоустойчивый итератор. Отказоустойчивые итераторы используют снимок размера карты, который нельзя редактировать во время итерации.


3
  1. Если согласованность данных очень важна - используйте Hashtable или Collections.synchronizedMap (Map).
  2. Если скорость / производительность очень важны и обновление данных может быть скомпрометировано - используйте ConcurrentHashMap.

2

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


1

Метод Collections.synchronizedMap () синхронизирует все методы HashMap и эффективно сводит его к структуре данных, в которую одновременно может входить один поток, поскольку он блокирует каждый метод с помощью общей блокировки.

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


1

ConcurrentHashMap был представлен как альтернатива Hashtable в Java 1.5 как часть пакета для параллелизма. С ConcurrentHashMap у вас есть лучший выбор не только в том случае, если он может безопасно использоваться в параллельной многопоточной среде, но также обеспечивает лучшую производительность, чем Hashtable и synchronizedMap. ConcurrentHashMap работает лучше, потому что он блокирует часть Map. Это позволяет согласованные операции чтения и в то же время поддерживает целостность путем синхронизации операций записи.

Как реализован ConcurrentHashMap

ConcurrentHashMap был разработан как альтернатива Hashtable и поддерживает все функциональные возможности Hashtable с дополнительными возможностями, так называемым уровнем параллелизма. ConcurrentHashMap позволяет нескольким читателям читать одновременно без использования блоков. Это становится возможным благодаря разделению Карты на разные части и блокированию только части Карты в обновлениях. По умолчанию уровень параллелизма равен 16, поэтому карта разбита на 16 частей, и каждая часть управляется отдельным блоком. Это означает, что 16 потоков могут работать с картой одновременно, если они работают с разными частями карты. Это делает производительность ConcurrentHashMap высокой, а не снижает потокобезопасность.

Если вас интересуют некоторые важные функции ConcurrentHashMap и когда вам следует использовать эту реализацию Map - я просто поставлю ссылку на хорошую статью - Как использовать ConcurrentHashMap в Java


0

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

Чтобы сделать Mapпоток безопасным, мы можем использоватьCollections.synchronizedMap оператор и ввести экземпляр карты в качестве параметра.

Реализация synchronizedMapв Collections, как показано ниже

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

Как видите, входной Mapобъект обернут SynchronizedMapобъектом.
Давайте копаться в реализации SynchronizedMap,

 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

Что SynchronizedMapможно суммировать как добавление одиночной блокировки к основному методу Mapобъекта ввода . Все методы, защищенные блокировкой, не могут быть доступны нескольким потокам одновременно. Это означает, что обычные операции, такие как putи getмогут выполняться одним потоком одновременно для всех данных вMap объекте.

Это делает Map поток объектов безопасным в настоящее время, но производительность может стать проблемой в некоторых сценариях.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.