Pourquoi il n'y a pas de ConcurrentHashSet contre ConcurrentHashMap

538

HashSet est basé sur HashMap.

Si nous regardons la HashSet<E>mise en œuvre, tout est géré sous HashMap<E,Object>.

<E>est utilisé comme clé de HashMap.

Et nous savons que ce HashMapn'est pas sûr pour les threads. C'est pourquoi nous avons ConcurrentHashMapen Java.

Sur cette base, je ne comprends pas pourquoi nous n'avons pas de ConcurrentHashSet qui devrait être basé sur le ConcurrentHashMap?

Y a-t-il autre chose qui me manque? Je dois utiliser Setdans un environnement multi-thread.

De plus, si je veux créer le mien, ConcurrentHashSetpuis-je le réaliser en remplaçant simplement le HashMapto ConcurrentHashMapet en laissant le reste tel quel?

Talha Ahmed Khan
la source
2
Après avoir regardé l'API, si je devais deviner, je dirais que cela semble se résumer à 2 facteurs, (1) éviter d'avoir à créer une classe dans l'API Java pour chaque petit peu de fonctionnalité nécessaire (2) Fournir des classes de commodité pour objets les plus fréquemment utilisés. Personnellement, je préfère LinkedHashMap et LinkedHashSet car ils garantissent que l'ordre est le même que l'ordre d'insertion, la seule raison d'utiliser un ensemble est d'éviter la duplication, souvent je veux toujours maintenir l'ordre d'insertion.
Ali
1
@Ali, je préfère personnellement LinkedHashMap et LinkedHashSet, vous irez loin :)
bestsss
9
Une question un peu ancienne, mais comme c'est le premier résultat dans Google, il peut être utile de savoir que ConcurrentSkipListSet a déjà l'implémentation de ConcurrentHashMap. Voir docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Igor Rodriguez
1
Ce que j'ai vu de la source Java ConcurrentSkipListSetest construit ConcurrentSkipListMap, qui implémente ConcurrentNavigableMapet ConcurrentMap.
Talha Ahmed Khan

Réponses:

581

Il n'y a pas de type intégré ConcurrentHashSetcar vous pouvez toujours dériver un ensemble d'une carte. Comme il existe de nombreux types de cartes, vous utilisez une méthode pour produire un ensemble à partir d'une carte (ou classe de carte) donnée.

Avant Java 8, vous produisez un ensemble de hachage simultané soutenu par une carte de hachage simultanée, en utilisant Collections.newSetFromMap(map)

Dans Java 8 (souligné par @Matt), vous pouvez obtenir une vue d' ensemble de hachage simultanée via ConcurrentHashMap.newKeySet(). C'est un peu plus simple que l'ancien newSetFromMapqui vous obligeait à passer dans un objet de carte vide. Mais c'est spécifique à ConcurrentHashMap.

Quoi qu'il en soit, les concepteurs Java auraient pu créer une nouvelle interface d'ensemble à chaque fois qu'une nouvelle interface de carte était créée, mais ce modèle serait impossible à appliquer lorsque des tiers créeraient leurs propres cartes. Il est préférable d'avoir les méthodes statiques qui dérivent de nouveaux ensembles; cette approche fonctionne toujours, même lorsque vous créez vos propres implémentations de carte.

Ray Toal
la source
4
Ai-je raison de dire que si vous créez l'ensemble de cette façon ConcurrentHashMap, vous perdez les avantages dont vous bénéficieriez ConcurrentHashMap?
Pacerier
19
Il n'y a aucun avantage à perdre. newSetFromMapL'implémentation se trouve à partir de la ligne 3841 dans docjar.com/html/api/java/util/Collections.java.html . C'est juste un wrapper ....
Ray Toal
4
@Andrew, je pense que la motivation derrière l'utilisation d'un "ConcurrentSet" ne vient pas de l'API mais plutôt de l'implémentation - sécurité des threads mais sans verrouillage universel - plusieurs lectures simultanées par exemple.
Ustaman Sangat
5
ConcurrentSkipList a beaucoup de surcharge (taille) et les recherches sont plus lentes.
eckes
3
soyez prudent lorsque vous utilisez cette approche, car certaines méthodes ne sont pas implémentées correctement. Suivez simplement les liens: Collections.newSetFromMapcrée un SetFromMap. par exemple, la SetFromMap.removeAllméthode délègue à la KeySetView.removeAll, qui hérite de ConcurrentHashMap$CollectionView.removeAll. Cette méthode est très inefficace pour retirer des éléments en bloc. imagine removeAll(Collections.emptySet())traverse tous les éléments du Mapsans rien faire. Avoir un ConcurrentHashSetqui est correctement implémenté sera mieux dans la plupart des cas.
benez
104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Masque Serge
la source
79

Avec Guava 15, vous pouvez également simplement utiliser:

Set s = Sets.newConcurrentHashSet();
kichik
la source
12
C'est toujours un cauchemar. Si vous avez un ensemble ou une carte qui n'indique pas si quelque chose est sûr ou non, vous trouverez toutes sortes de dangers et de désastres qui se produisent en maintenance. Je voudrais toujours un type qui indique la sécurité des threads pour les collections (ou non).
Martin Kersten
11
La description de la méthode est littéralement "Crée un ensemble thread-safe soutenu par une carte de hachage"
kichik
16
Comme je l'ai dit, il manque un ConcurrentSet <E>. ConcurrentHashMap est fourni avec une interface ConcurrentMap pour l'indiquer. C'est la même raison pour laquelle j'ajoute toujours cette interface ConcurrentSet également.
Martin Kersten
35

Comme Ray Toal l'a mentionné, c'est aussi simple que:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
BullyWiiPlaza
la source
1
Cela semble nécessiter Java 8. En ce qui concerne l'implémentation, cela semble également n'être qu'un emballage de ConcurrentHashMap.
Mygod
20

Il semble que Java fournisse une implémentation Set simultanée avec son ConcurrentSkipListSet . Un ensemble SkipList n'est qu'un type spécial d'implémentation d'ensemble. Il implémente toujours les interfaces Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Cela peut fonctionner pour vous si vous n'avez besoin que de l'interface Set.

Mike Pone
la source
12
Notez que ConcurrentSkipListSetles éléments doivent êtreComparable
user454322
Si vous devez étendre à partir d'un ensemble qui est simultané, c'est la seule solution ici qui fonctionnera.
ndm13
ConcurrentSkipListMap ajoute une pénalité de performance inutile d'avoir l'arborescence comme structure de données de base, au lieu d'utiliser HashTable, même lorsque vous n'avez pas besoin de fonctionnalité de tri / navigation.
Ajeet Ganga
n'utilisez pas ConcurrentSkipListSetsauf si vous voulez un SortedSet. Une opération habituelle comme ajouter ou supprimer devrait être O (1) pour a HashSet, mais O (log (n)) pour a SortedSet.
benez
16

Comme indiqué par cela, le meilleur moyen d'obtenir un HashSet compatible avec la concurrence est deCollections.synchronizedSet()

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

Cela a fonctionné pour moi et je n'ai vu personne le montrer du doigt.

EDITER Ceci est moins efficace que la solution actuellement approuvée, comme le souligne Eugene, car elle enveloppe simplement votre ensemble dans un décorateur synchronisé, tandis qu'un ConcurrentHashMapimplémente en fait une concurrence de bas niveau et peut sauvegarder votre ensemble tout aussi bien. Merci donc à M. Stepanenkov de l'avoir précisé.

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

Nirro
la source
16
la synchronizedSetméthode crée simplement le décorateur sous Collectionpour envelopper des méthodes qui pourraient être thread-safe en synchronisant toute la collection. Mais ConcurrentHashMapest implémenté à l'aide d' algorithmes non bloquants et de synchronisations "de bas niveau" sans aucun verrouillage de l'ensemble de la collection. Ainsi, les enveloppements de Collections.synchronized... sont pires dans les environnements multi-threads pour des raisons de performances.
Eugene Stepanenkov
12

Vous pouvez utiliser des goyaves pour Sets.newSetFromMap(map)en obtenir un. Java 6 a également cette méthode dansjava.util.Collections

Bozho
la source
il est disponible dans java.utll.Collections et l'ensemble de CHM est généralement une mauvaise chose de toute façon.
bestsss
oui, j'ai remarqué qu'il est ajouté dans Java 6, alors l'ajoute à la réponse
Bozho
Le principal c'est que si c'est ThreadSafe, et j'en doute vraiment.
Talha Ahmed Khan
@Talha, c'est sûr pour les threads, mais la sécurité des threads à elle seule ne signifie rien
bestsss
Parfois, cela signifie tout. Son problème est lié aux performances, sauf s'il fait partie d'un algorithme généralement implémenté de manière à minimiser le besoin de mappage simultané.
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;
   }
}
MARYLAND. Mohiuddin Ahmed
la source
2
J'aime l'idée d'utiliser Boolean.TRUE au lieu d'un objet factice. C'est un peu plus élégant. L'utilisation de NULL est également possible car elle serait disponible dans le jeu de clés même si elle était mappée sur null.
Martin Kersten
2
@MartinKersten fyi, ConcurrentHashMap n'autorise pas les valeurs nulles
Lauri Lehtinen
2

Pourquoi ne pas utiliser: CopyOnWriteArraySet de java.util.concurrent?

Shendor
la source
6
Parce que CopyOnWriteArraySet copie la collection entière sur n'importe quelle mutation d'état, ce qui n'est pas toujours souhaité en raison de l'impact sur les performances. Il est conçu pour fonctionner uniquement dans des cas spéciaux.
boneash