Какая разница между ConcurrentHashMap и Collections.synchronizedMap(Карта)?

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

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

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

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

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

Ответ 1

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

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

Ответ 2

╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   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 блокирует только ведро.

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

Также еще одно отличие состоит в том, что ConcurrentHashMap не сохранит порядок элементов в переданной карте. Он похож на HashMap при хранении данных. Нет гарантии, что порядок элементов сохранен. Пока Collections.synchronizedMap() сохранит порядок элементов переданной карты. Например, если вы передаете TreeMap в ConcurrentHashMap, порядок элементов в ConcurrentHashMap может отличаться от порядка в TreeMap, но Collections.synchronizedMap() сохранит порядок.

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

Существует одно сообщение, которое демонстрирует различия этих двух, а также ConcurrentSkipListMap.

Ответ 6

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

Ответ 7

  • Hashtable и ConcurrentHashMap не разрешать клавиши null или null.

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

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

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

Ответ 8

Вы правы насчет 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, как показано выше. Прочтите эту статью, чтобы лучше понять ее тонкости.

Ответ 9

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

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

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

Ответ 10

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

  • synchronizedHashmap и Hashtable

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

  1. ConcurrentHashMap

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

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

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

Ответ 11

ConcurrentHashMap

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

SynchronizedHashMap

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

источник

Ответ 12

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

Синхронизированная карта также не очень отличается от 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, в то время как в случае ConcurrentHashMap безопасность потоков достигается путем деления всей карты на другой раздел на основе уровня параллелизма и только блокировка конкретной части вместо блокировки всей карты.

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

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

Link1

Link2

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

Ответ 13

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

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

Ответ 14

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

Ответ 15

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

Ответ 16

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

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

Ответ 17

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

Ответ 18

Кроме того, что было предложено, я хотел бы опубликовать исходный код, относящийся к 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 для деталей. В двух словах она была реализована с учетом безопасности потоков и производительности.