Чому немає ConcurrentHashSet проти ConcurrentHashMap


537

HashSet заснований на HashMap.

Якщо ми подивимось на HashSet<E>реалізацію, все керовано в рамках HashMap<E,Object>.

<E>використовується як ключ від HashMap.

І ми знаємо, що HashMapце не безпечно для ниток. Ось чому ми маємо ConcurrentHashMapна Java.

Виходячи з цього, я плутаю, що чому ми не маємо ConcurrentHashSet, який повинен базуватися на ConcurrentHashMap?

Чи є ще щось, чого я пропускаю? Мені потрібно використовувати Setв багатопотоковому середовищі.

Крім того , якщо я хочу , щоб створити свій власний ConcurrentHashSetя можу домогтися цього, просто замінивши HashMapна ConcurrentHashMapі залишити решту як є?


2
Подивившись API, якби я здогадувався, я б сказав, що це, здається, зводиться до 2 факторів, (1) уникаючи необхідності створювати клас в Java API для кожного необхідного функціоналу (2) Забезпечення класів зручності для більш часто використовувані об'єкти. Я особисто віддаю перевагу LinkedHashMap та LinkedHashSet, оскільки вони гарантують порядок такий самий, як порядок вставки, єдиною причиною використання набору є уникнення дублювання, часто я все ж хочу підтримувати порядок вставки.
Алі

1
@Ali, я особисто віддаю перевагу 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
@Andrew, я думаю, що мотивація використання "ConcurrentSet" випливає не з API, а швидше з реалізації - безпеки потоку, але без універсального блокування - декількох одночасних зчитувань, наприклад.
Устаман Сангат

5
ConcurrentSkipList має велику кількість (розмір) накладних витрат, і пошуки проходять повільніше.
eckes

3
будьте обережні при використанні цього підходу, оскільки деякі методи не реалізовані правильно. Просто перейдіть за посиланнями: Collections.newSetFromMapстворює SetFromMap. наприклад, SetFromMap.removeAllметод делегує тим KeySetView.removeAll, що успадковує від ConcurrentHashMap$CollectionView.removeAll. Цей спосіб вкрай неефективний при об'ємному видаленні елементів. уявіть собі, що removeAll(Collections.emptySet())обходить всі елементи в цьому, Mapне роблячи нічого. Маючи ConcurrentHashSetщо corretly реалізований буде краще в більшості випадків.
Бенез


79

З Guava 15 ви також можете просто використовувати:

Set s = Sets.newConcurrentHashSet();

12
Це завжди кошмар. Якщо у вас є набір або карта, яка не вказує на те, чи є щось безпечним для ниток, ви знайдете всі види небезпек і катастроф, які трапляються у службі. Я завжди хотів би тип, який вказує на безпеку ниток для колекцій (чи ні).
Мартін Керстен

11
Опис методу дослівно "Створює безпечний набір, підкріплений хеш-картою"
kichik

16
Як я вже говорив, параметр ConcurrentSet <E> відсутній. ConcurrentHashMap поставляється разом із інтерфейсом ConcurrentMap для позначення цього. Це та сама причина, що я завжди додаю цей інтерфейс ConcurrentSet.
Мартін Керстен

35

Як згадував Рей Тол, це так просто, як:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();

1
Здається, для цього потрібна Java 8. Дивлячись на реалізацію, це також здається просто обгорткою ConcurrentHashMap.
Мигод

20

Схоже, Java забезпечує одночасну реалізацію набору зі своїм ConcurrentSkipListSet . Набір SkipList - це лише особливий вид реалізації набору. Він все ще реалізує інтерфейси Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Це може працювати для вас, якщо вам потрібен лише інтерфейс Set.


12
Зауважте, що ConcurrentSkipListSetелементами '' повинен бутиComparable
user454322

Якщо вам потрібно продовжити з одночасного набору, це єдине рішення, яке буде спрацьовувати тут.
ndm13

ConcurrentSkipListMap додає непотрібне покарання продуктивності того, щоб дерево було базовою структурою даних, замість того, щоб використовувати HashTable, навіть коли вам не потрібна функція сортування / навігації.
Ajeet Ganga

не використовуйте, ConcurrentSkipListSetякщо ви не хочете SortedSet. Звичайна операція, як додавання або видалення, повинна бути O (1) для a HashSet, але O (log (n)) для a SortedSet.
Бенез

16

Як зазначено на це найкращий спосіб , щоб отримати паралелізм-можливість HashSet це за допомогоюCollections.synchronizedSet()

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

Це працювало на мене, і я не бачив, щоб хтось насправді вказував на це.

EDIT Це менш ефективно, ніж прийняте на даний момент рішення, як зазначає Євгеній, оскільки воно просто перетворює ваш набір в синхронізований декоратор, а ConcurrentHashMapнасправді реалізує низькорівневу одночасність і може повернути ваш набір так само добре. Тож спасибі пану Степаненкову за те, що він зрозумів це.

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


16
synchronizedSetметод просто створює декоратор під Collectionметоди обгортки , які можуть бути поточно-синхронізацією всій колекції. Але ConcurrentHashMapреалізується за допомогою алгоритмів, що не блокують, і "низького рівня" синхронізації без блокування всієї колекції. Тож обгортки від Collections.synchronized... гірше в багатопотокових середовищах з міркувань продуктивності.
Євген Степаненков

12

Ви можете використовувати гуави, Sets.newSetFromMap(map)щоб отримати її. Java 6 також має цей метод вjava.util.Collections


це доступно в java.utll.Collections і набір 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;
   }
}

2
Мені подобається ідея використовувати Boolean.TRUE замість манекена. Це трохи елегантніше. Також використання NULL також можливе, оскільки воно буде доступне в наборі ключів, навіть якщо воно буде зіставлено на null.
Мартін Керстен

2
@MartinKersten fyi, ConcurrentHashMap не дозволяє нульових значень
Lauri Lehtinen

2

Чому б не використовувати: CopyOnWriteArraySet від java.util.concurrent?


6
Оскільки CopyOnWriteArraySet копіює всю колекцію на будь-яку мутацію стану, яка не завжди потрібна через вплив на продуктивність. Він призначений для роботи лише в особливих випадках.
кістка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.