В чем разница между ConcurrentHashMap и коллекциями.synchronizedMap(карта)?


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

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

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

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

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

18 536

18 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Hashtable и ConcurrentHashMap не допускать null ключи или null значения.

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

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

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

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

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

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

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

вот некоторые из них :

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

этот текст из разница между ConcurrentHashMap и hashtable в Java

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

  1. synchronisedHashmap и Hashtable

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

  1. ConcurrentHashMap

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

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

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

ConcurrentHashMap

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

SynchronizedHashMap

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

источник

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

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

Синхронизировать Карте:

Synchronized Map также не очень отличается от Hashtable и обеспечивает аналогичную производительность в параллельных программах Java. Единственное различие между Hashtable и SynchronizedMap заключается в том, что SynchronizedMap не является наследием, и вы можете обернуть любую карту, чтобы создать ее синхронизированную версию с помощью коллекций.synchronizedMap() метод.

ConcurrentHashMap:

класс ConcurrentHashMap предоставляет параллельная версия стандартной хэш-карты. Это улучшение функциональности synchronizedMap, предоставляемой в классе Collections.

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

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

ConcurrentHashMap не бросает ConcurrentModificationException

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

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

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

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

подобные ссылки

Link1

Link2

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

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

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

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

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

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

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

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

реализация synchronizedMap на Collections как ниже

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

как вы можете видеть, вход Map объект обернут