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

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

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

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

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

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

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) - они используют очень простой синхронизации, что означает, что только один поток может получить доступ к карте одновременно.

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

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


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

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

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

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


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

также одно другое различие заключается в том, что ConcurrentHashMap не будет сохранить порядок элементов в переданной карте. Это похоже на HashMap при сохранении данных. Нет никакой гарантии, что порядок элементов сохраняется. В то время как Collections.synchronizedMap() сохранит порядок элементов переданной карты. Например, если вы передаете TreeMap to 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 (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

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

источник


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

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


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

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

ConcurrentHashMap:

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

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

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

ConcurrentHashMap не бросает ConcurrentModificationException

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

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

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

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

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

link1 и

Link2

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


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


  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 объект обернут