Perché non esiste ConcurrentHashSet contro ConcurrentHashMap


538

HashSet si basa su HashMap.

Se guardiamo HashSet<E>all'implementazione, tutto è gestito sotto HashMap<E,Object>.

<E>è usato come chiave di HashMap.

E sappiamo che HashMapnon è thread-safe. Ecco perché abbiamo ConcurrentHashMapin Java.

Sulla base di questo, sono confuso dal fatto che non abbiamo un ConcurrentHashSet che dovrebbe essere basato sul ConcurrentHashMap?

C'è qualcos'altro che mi manca? Devo usare Setin un ambiente multi-thread.

Inoltre, se voglio crearne uno mio ConcurrentHashSetposso ottenerlo semplicemente sostituendo il HashMapto ConcurrentHashMape lasciando il resto com'è?


2
Dopo aver esaminato l'API, se dovessi indovinare, direi che sembra dipendere da 2 fattori, (1) evitando di dover creare una classe nell'API Java per ogni piccola funzionalità necessaria (2) Fornire classi di convenienza per oggetti usati più frequentemente. Personalmente preferisco LinkedHashMap e LinkedHashSet poiché garantiscono che l'ordine è lo stesso dell'ordine di inserzione, l'unico motivo per utilizzare un set è evitare duplicazioni, spesso voglio comunque mantenere l'ordine di inserzione.
Ali,

1
@Ali, personalmente preferisco LinkedHashMap e LinkedHashSet andrai lontano :)
bestsss

9
Una domanda un po 'vecchia, ma poiché è il primo risultato in Google, può essere utile sapere che ConcurrentSkipListSet ha già l'implementazione di ConcurrentHashMap. Vedi docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Igor Rodriguez,

1
Quello che ho visto dal sorgente Java ConcurrentSkipListSetè basato su ConcurrentSkipListMap, che implementa ConcurrentNavigableMape ConcurrentMap.
Talha Ahmed Khan,

Risposte:


581

Non esiste un tipo incorporato per ConcurrentHashSetperché puoi sempre derivare un set da una mappa. Poiché esistono molti tipi di mappe, si utilizza un metodo per produrre un set da una determinata mappa (o classe di mappe).

Prima di Java 8, si produce un set di hash simultaneo supportato da una mappa di hash simultanea, usando Collections.newSetFromMap(map)

In Java 8 (sottolineato da @Matt), è possibile ottenere una vista insieme di hash simultanea tramite ConcurrentHashMap.newKeySet(). Questo è un po 'più semplice rispetto al vecchio newSetFromMapche richiedeva il passaggio in un oggetto cartografico vuoto. Ma è specifico per ConcurrentHashMap.

In ogni caso, i progettisti Java avrebbero potuto creare una nuova interfaccia set ogni volta che veniva creata una nuova interfaccia mappa, ma tale modello sarebbe impossibile da applicare quando terze parti creano le proprie mappe. È meglio avere i metodi statici che derivano nuovi set; tale approccio funziona sempre, anche quando crei le tue implementazioni di mappe.


4
Ho ragione a dire che se crei il set in questo modo ConcurrentHashMap, perdi i benefici che otterrai ConcurrentHashMap?
Pacerier

19
Non ci sono benefici da perdere. newSetFromMapL'implementazione si trova a partire dalla linea 3841 in docjar.com/html/api/java/util/Collections.java.html . È solo un involucro ....
Ray Toal,

4
@Andrew, penso che la motivazione dietro l'utilizzo di un "ConcurrentSet" non derivi dall'API ma piuttosto dall'implementazione - sicurezza del thread ma senza un blocco universale - ad esempio letture multiple simultanee .
Ustaman Sangat,

5
ConcurrentSkipList ha un sacco di overhead (dimensioni) e le ricerche sono più lente.
Verifica il

3
fare attenzione quando si utilizza questo approccio, poiché alcuni metodi non sono implementati correttamente. Basta seguire i collegamenti: Collections.newSetFromMapcrea un SetFromMap. ad esempio il SetFromMap.removeAllmetodo viene delegato a KeySetView.removeAll, che eredita da ConcurrentHashMap$CollectionView.removeAll. Questo metodo è altamente inefficiente nella rimozione di elementi in blocco. immaginare removeAll(Collections.emptySet())attraversa tutti gli elementi nel Mapsenza fare nulla. Avere un ConcurrentHashSetche è correttamente implementato sarà meglio nella maggior parte dei casi.
Benez

104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

79

Con Guava 15 puoi anche semplicemente usare:

Set s = Sets.newConcurrentHashSet();

12
Questo è sempre un incubo. Se hai un set o una mappa che non indica se qualcosa è sicuro per i thread, troverai tutti i tipi di pericoli e disastri che si verificano nella manutenzione. Vorrei sempre un tipo che indica la sicurezza del thread per le raccolte (o meno).
Martin Kersten,

11
La descrizione del metodo è letteralmente "Crea un set thread-safe supportato da una mappa hash"
kichik

16
Come ho detto, manca un ConcurrentSet <E>. ConcurrentHashMap viene fornito con un'interfaccia ConcurrentMap per indicare questo. Questo è lo stesso motivo per cui aggiungo sempre anche questa interfaccia ConcurrentSet.
Martin Kersten,

35

Come ha detto Ray Toal , è facile come:

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

1
Questo sembra richiedere Java 8. Guardando l'implementazione, anche questo sembra essere solo un wrapper di ConcurrentHashMap.
Mygod,

20

Sembra che Java fornisca un'implementazione di Set simultanea con il suo ConcurrentSkipListSet . Un set SkipList è solo un tipo speciale di implementazione del set. Implementa ancora le interfacce Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Questo potrebbe funzionare per te se hai solo bisogno dell'interfaccia Set.


12
Si noti che ConcurrentSkipListSetgli elementi dovrebbero essereComparable
user454322

Se è necessario estendere da un set simultaneo, questa è l'unica soluzione che funzionerà.
ndm13,

ConcurrentSkipListMap aggiunge penalità di prestazioni non necessarie per avere l'albero come struttura di dati di base, invece di utilizzare HashTable, anche quando non è necessaria la funzionalità di ordinamento / navigazione.
Ajeet Ganga,

non usare a ConcurrentSkipListSetmeno che tu non voglia un SortedSet. Una normale operazione come aggiungere o rimuovere dovrebbe essere O (1) per a HashSet, ma O (log (n)) per a SortedSet.
Benez

16

Come indicato da questo, il modo migliore per ottenere un HashSet in grado di competere è tramiteCollections.synchronizedSet()

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

Questo ha funzionato per me e non ho visto nessuno davvero indicarlo.

EDIT Questo è meno efficiente della soluzione attualmente approvata, come sottolinea Eugene, dal momento che avvolge il tuo set in un decoratore sincronizzato, mentre in ConcurrentHashMaprealtà implementa una concorrenza di basso livello e può supportare il tuo Set altrettanto bene. Quindi grazie a Mr. Stepanenkov per averlo chiarito.

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


16
il synchronizedSetmetodo crea solo il decoratore sotto Collectionai metodi avvolgere che potrebbero essere thread-safe per la sincronizzazione l'intera collezione. Ma ConcurrentHashMapè implementato usando algoritmi non bloccanti e sincronizzazioni "di basso livello" senza alcun blocco dell'intera collezione. Quindi i wrapper di Collections.synchronized... è peggio negli ambienti multi-thread per motivi di prestazioni.
Eugene Stepanenkov,

12

Puoi usare guava Sets.newSetFromMap(map)per ottenerne uno. Anche Java 6 ha questo metodojava.util.Collections


è disponibile in java.utll.Collections e insieme di CHM di solito è comunque una cosa negativa.
bestsss

sì, ho notato che è stato aggiunto in Java 6, quindi l'ho aggiunto alla risposta
Bozho

Il principale è che se è ThreadSafe, e ne dubito davvero.
Talha Ahmed Khan,

@Talha, è thread-safe, tuttavia la sicurezza del thread da sola non significa nulla
bestsss

A volte significa tutto. Si tratta di un problema di prestazioni a meno che non faccia parte di un algoritmo che di solito viene implementato in modo da ridurre al minimo la necessità di mappatura simultanea.
Martin Kersten,

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
Mi piace l'idea di usare Boolean.TRUE invece di un oggetto fittizio. È un po 'più elegante. Anche l'uso di NULL è anche possibile poiché sarebbe disponibile nel set di chiavi anche se mappato su null.
Martin Kersten,

2
@MartinKersten fyi, ConcurrentHashMap non consente valori null
Lauri Lehtinen

2

Perché non usare: CopyOnWriteArraySet da java.util.concurrent?


6
Perché CopyOnWriteArraySet copia l'intera raccolta su qualsiasi mutazione di stato, che non è sempre desiderata a causa dell'impatto sulle prestazioni. È progettato per funzionare solo in casi speciali.
Boneash,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.