Почему нет ConcurrentHashSet против ConcurrentHashMap

538

HashSet основан на HashMap.

Если мы посмотрим на HashSet<E>реализацию, все было под управлением HashMap<E,Object>.

<E>используется в качестве ключа HashMap.

И мы знаем, что HashMapэто не потокобезопасно. Вот почему у нас ConcurrentHashMapв Java.

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

Есть ли что-то еще, что мне не хватает? Мне нужно использовать Setв многопоточной среде.

Кроме того , если я хочу , чтобы создать свой собственный ConcurrentHashSetя могу добиться этого, просто заменив HashMapна ConcurrentHashMapи оставить остальное как есть?

Талха Ахмед Хан
источник
2
Посмотрев на API, если бы я догадался, я бы сказал, что он сводится к двум факторам: (1) избегать необходимости создавать класс в Java API для каждого небольшого количества необходимой функциональности (2) Предоставлять удобные классы для более часто используемые объекты. Я лично предпочитаю LinkedHashMap и LinkedHashSet, так как они гарантируют, что порядок такой же, как порядок вставки, единственная причина использования набора состоит в том, чтобы избежать дублирования, часто я все еще хочу поддерживать порядок вставки.
Али
1
@ Али, я лично предпочитаю LinkedHashMap и LinkedHashSet, вы далеко пойдете :)
bestsss
9
Немного старый вопрос, но поскольку это первый результат в Google, может быть полезно знать, что ConcurrentSkipListSet уже имеет реализацию ConcurrentHashMap. См. Docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Игорь Родригес
1
То, что я видел из исходного кода Java, основано ConcurrentSkipListSetна том ConcurrentSkipListMap, что реализует ConcurrentNavigableMapи ConcurrentMap.
Талха Ахмед Хан

Ответы:

581

Там нет встроенного типа для, ConcurrentHashSetпотому что вы всегда можете извлечь набор из карты. Поскольку существует много типов карт, вы используете метод для создания набора из данной карты (или класса карты).

До Java 8 вы создавали параллельный хеш-набор, поддерживаемый одновременной хэш-картой, используя Collections.newSetFromMap(map)

В Java 8 (указанный) @ Matt, вы можете получить одновременный хэш набор вида через ConcurrentHashMap.newKeySet(). Это немного проще, чем старый, newSetFromMapкоторый требовал от вас передать пустой объект карты. Но это специфично для ConcurrentHashMap.

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

Рэй Тоал
источник
4
Прав ли я сказать, что если вы создадите набор таким образом ConcurrentHashMap, вы потеряете те преимущества, которые получили бы ConcurrentHashMap?
Pacerier
19
Там нет преимуществ, чтобы потерять. newSetFromMapРеализация начинается со строки 3841 в docjar.com/html/api/java/util/Collections.java.html . Это просто обертка ....
Рэй Тоал
4
@ Андрей, я думаю, что мотивация использования ConcurrentSet связана не с API, а с реализацией - безопасностью потоков, но без универсальной блокировки - например, с несколькими одновременными чтениями.
Устаман Сангат
5
ConcurrentSkipList имеет много (размер) накладных расходов, и поиск медленнее.
Eckes
3
Будьте осторожны при использовании этого подхода, так как некоторые методы не реализованы правильно. Просто перейдите по ссылкам: Collections.newSetFromMapсоздает SetFromMap. например, SetFromMap.removeAllметод делегирует то KeySetView.removeAll, что наследуется от ConcurrentHashMap$CollectionView.removeAll. Этот метод крайне неэффективен при удалении большого количества элементов. Представьте, что вы removeAll(Collections.emptySet())пересекаете все элементы, Mapничего не делая. Имея ConcurrentHashSetчто corretly реализован будет лучше в большинстве случаев.
Benez
104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Серж маск
источник
79

С Guava 15 вы также можете просто использовать:

Set s = Sets.newConcurrentHashSet();
кичик
источник
12
Это всегда кошмар. Если у вас есть набор или карта, на которой не указано, является ли что-то потокобезопасным, вы обнаружите, что все виды опасностей и бедствий происходят в процессе обслуживания. Я всегда хотел бы тип, который указывает потокобезопасность для коллекций (или нет).
Мартин Керстен
11
Описание метода буквально «Создает потокобезопасный набор, поддерживаемый хэш-картой»
kichik
16
Как я уже сказал, отсутствует ConcurrentSet <E>. ConcurrentHashMap поставляется с интерфейсом ConcurrentMap, чтобы указать это. По этой же причине я всегда добавляю и этот интерфейс ConcurrentSet.
Мартин Керстен
35

Как упомянул Рэй Тоал, это так же просто, как:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
BullyWiiPlaza
источник
1
Кажется, для этого требуется Java 8. Что касается реализации, то это также кажется просто оберткой ConcurrentHashMap.
Mygod
20

Похоже, что Java предоставляет параллельную реализацию Set со своим ConcurrentSkipListSet . Набор SkipList - это просто особая реализация набора. Он по-прежнему реализует интерфейсы Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Это может работать для вас, если вам нужен только интерфейс Set.

Майк Пон
источник
12
Обратите внимание, что ConcurrentSkipListSetэлементы должны бытьComparable
user454322
Если вам нужно расширить набор одновременно, это будет единственное решение, которое будет работать.
ndm13
ConcurrentSkipListMap добавляет ненужное снижение производительности из-за наличия дерева в качестве базовой структуры данных вместо использования HashTable, даже если вам не нужны функции сортировки / навигации.
Аджит Ганга
не используйте, ConcurrentSkipListSetесли вы не хотите SortedSet. Обычная операция, такая как add или remove, должна быть O (1) для a HashSet, но O (log (n)) для a SortedSet.
Benez
16

Как указано на это лучший способ , чтобы получить параллелизм-возможность HashSet это с помощьюCollections.synchronizedSet()

Set s = Collections.synchronizedSet(new HashSet(...));

Это сработало для меня, и я не видел, чтобы кто-то действительно указывал на это.

РЕДАКТИРОВАТЬ Это менее эффективно, чем одобренное в настоящее время решение, как отмечает Юджин, поскольку оно просто оборачивает ваш набор в синхронизированный декоратор, в то время как ConcurrentHashMapфактически реализует низкоуровневый параллелизм и может поддерживать ваш Set так же хорошо. Так что спасибо г-ну Степаненкову за то, что он это прояснил.

http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedSet-java.util.Set-

Nirro
источник
16
synchronizedSetметод просто создает декоратор под Collectionметоды обертки , которые могут быть поточно-синхронизацией всей коллекции. Но ConcurrentHashMapреализован с использованием неблокирующих алгоритмов и «низкоуровневых» синхронизаций без каких-либо блокировок всей коллекции. Так что переносчики из Collections.synchronized... хуже в многопоточных средах по соображениям производительности.
Евгений Степаненков
12

Вы можете использовать гуаву, Sets.newSetFromMap(map)чтобы получить один. Java 6 также имеет этот метод вjava.util.Collections

Bozho
источник
он доступен в java.utll. Коллекции и набор CHM, как правило, плохо в любом случае.
bestsss
да, я заметил, что это добавлено в Java 6, поэтому добавил его к ответу
Божо
Главное, что это ThreadSafe, и я действительно в этом сомневаюсь.
Талха Ахмед Хан
@Talha, это потокобезопасный, однако безопасность потоков сама по себе ничего не значит
bestsss
Иногда это значит все. Это проблема производительности, если только она не является частью алгоритма, который обычно реализуется таким образом, что необходимость одновременного отображения сводится к минимуму.
Мартин Керстен
5
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>{
   private final ConcurrentMap<E, Object> theMap;

   private static final Object dummy = new Object();

   public ConcurrentHashSet(){
      theMap = new ConcurrentHashMap<E, Object>();
   }

   @Override
   public int size() {
      return theMap.size();
   }

   @Override
   public Iterator<E> iterator(){
      return theMap.keySet().iterator();
   }

   @Override
   public boolean isEmpty(){
      return theMap.isEmpty();
   }

   @Override
   public boolean add(final E o){
      return theMap.put(o, ConcurrentHashSet.dummy) == null;
   }

   @Override
   public boolean contains(final Object o){
      return theMap.containsKey(o);
   }

   @Override
   public void clear(){
      theMap.clear();
   }

   @Override
   public boolean remove(final Object o){
      return theMap.remove(o) == ConcurrentHashSet.dummy;
   }

   public boolean addIfAbsent(final E o){
      Object obj = theMap.putIfAbsent(o, ConcurrentHashSet.dummy);
      return obj == null;
   }
}
MD. Мохиуддин Ахмед
источник
2
Мне нравится идея использовать Boolean.TRUE вместо фиктивного объекта. Это немного элегантнее. Также возможно использование NULL, так как оно будет доступно в наборе ключей, даже если отображается на NULL.
Мартин Керстен
2
@MartinKersten fyi, ConcurrentHashMap не допускает нулевых значений
Лаури Лехтинен,
2

Почему бы не использовать: CopyOnWriteArraySet из java.util.concurrent?

Shendor
источник
6
Потому что CopyOnWriteArraySet копирует всю коллекцию при любой мутации состояния, что не всегда требуется из-за влияния на производительность. Он предназначен для работы только в особых случаях.
boneash