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