Por qué no hay ConcurrentHashSet contra ConcurrentHashMap

538

HashSet se basa en HashMap.

Si nos fijamos en la HashSet<E>implementación, todo se gestiona bajo HashMap<E,Object>.

<E>se usa como clave de HashMap.

Y sabemos que HashMapno es seguro para subprocesos. Por eso tenemos ConcurrentHashMapen Java.

En base a esto, estoy confundido porque no tenemos un ConcurrentHashSet que debería basarse en el ConcurrentHashMap?

¿Hay algo más que me estoy perdiendo? Necesito usar Seten un entorno multiproceso.

Además, si quiero crear el mío, ConcurrentHashSet¿puedo lograrlo simplemente reemplazando el HashMapto ConcurrentHashMapy dejando el resto como está?

Talha Ahmed Khan
fuente
2
Después de mirar la API, si tuviera que adivinar, diría que parece reducirse a 2 factores, (1) evitar tener que crear una clase en la API de Java para cada pequeña funcionalidad necesaria (2) Proporcionar clases de conveniencia para objetos usados ​​con más frecuencia. Personalmente, prefiero LinkedHashMap y LinkedHashSet, ya que garantizan que el orden es el mismo que el orden de inserción, la única razón para usar un conjunto es evitar la duplicación, a menudo todavía quiero mantener el orden de inserción.
Ali
1
@ Ali, personalmente prefiero LinkedHashMap y LinkedHashSet
llegarás
99
Una pregunta un poco antigua, pero como es el primer resultado en Google, puede ser útil saber que ConcurrentSkipListSet ya tiene la implementación de ConcurrentHashMap. Ver docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Igor Rodriguez
1
Lo que vi de la fuente de Java ConcurrentSkipListSetse basa ConcurrentSkipListMap, que implementa ConcurrentNavigableMapy ConcurrentMap.
Talha Ahmed Khan

Respuestas:

581

No hay un tipo incorporado ConcurrentHashSetporque siempre puedes derivar un conjunto de un mapa. Como hay muchos tipos de mapas, utiliza un método para producir un conjunto a partir de un mapa determinado (o clase de mapa).

Antes de Java 8, produce un conjunto de hash simultáneo respaldado por un mapa de hash simultáneo, mediante el uso de Collections.newSetFromMap(map)

En Java 8 (señalado por @Matt), puede obtener una vista de conjunto de hash simultáneo a través de ConcurrentHashMap.newKeySet(). Esto es un poco más simple que el anterior newSetFromMapque requería que pasaras un objeto de mapa vacío. Pero es específico de ConcurrentHashMap.

De todos modos, los diseñadores de Java podrían haber creado una nueva interfaz de conjunto cada vez que se creó una nueva interfaz de mapa, pero ese patrón sería imposible de aplicar cuando terceros creen sus propios mapas. Es mejor tener los métodos estáticos que derivan nuevos conjuntos; ese enfoque siempre funciona, incluso cuando crea sus propias implementaciones de mapas.

Ray Toal
fuente
44
¿Tengo razón al decir que si crea el conjunto de esta manera ConcurrentHashMap, pierde los beneficios que obtendría ConcurrentHashMap?
Pacerier
19
No hay beneficios que perder. newSetFromMapLa implementación se encuentra a partir de la línea 3841 en docjar.com/html/api/java/util/Collections.java.html . Es solo una envoltura ...
Ray Toal
44
@ Andrew, creo que la motivación detrás del uso de un "ConcurrentSet" se deriva no de la API sino de la implementación (seguridad de subprocesos pero sin un bloqueo universal), por ejemplo , múltiples lecturas concurrentes .
Ustaman Sangat el
55
ConcurrentSkipList tiene mucha sobrecarga (de tamaño) y las búsquedas son más lentas.
eckes
3
tenga cuidado al usar este enfoque, ya que algunos métodos no se implementan correctamente. Simplemente siga los enlaces: Collections.newSetFromMapcrea a SetFromMap. por ejemplo, el SetFromMap.removeAllmétodo delega a KeySetView.removeAll, que hereda de ConcurrentHashMap$CollectionView.removeAll. Este método es altamente ineficiente en la eliminación de elementos a granel. imagine removeAll(Collections.emptySet())atraviesa todos los elementos Mapsin hacer nada. Tener una ConcurrentHashSetimplementación correcta será mejor en la mayoría de los casos.
benez
104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Serge Mask
fuente
79

Con Guava 15 también puedes usar simplemente:

Set s = Sets.newConcurrentHashSet();
kichik
fuente
12
Esto siempre es una pesadilla. Si tiene un conjunto o un mapa que no indica si algo es seguro para los hilos o no, encontrará todo tipo de peligros y desastres que suceden. Siempre quisiera un tipo que indique la seguridad del hilo para colecciones (o no).
Martin Kersten
11
La descripción del método es literalmente "Crea un conjunto seguro para subprocesos respaldado por un mapa hash"
kichik
16
Como dije, falta un ConcurrentSet <E>. ConcurrentHashMap viene junto con una interfaz ConcurrentMap para indicar esto. Esta es la misma razón por la que siempre agrego esta interfaz ConcurrentSet también.
Martin Kersten
35

Como Ray Toal mencionó, es tan fácil como:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
BullyWiiPlaza
fuente
1
Esto parece requerir Java 8. En cuanto a la implementación, esto también parece ser solo una envoltura ConcurrentHashMap.
Mygod
20

Parece que Java proporciona una implementación de conjunto concurrente con su ConcurrentSkipListSet . Un conjunto SkipList es solo un tipo especial de implementación de conjunto. Todavía implementa las interfaces Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Esto podría funcionar para usted si solo necesita la interfaz Set.

Mike Pone
fuente
12
Tenga en cuenta que ConcurrentSkipListSetlos elementos deben serComparable
user454322
Si necesita extender desde un Conjunto que es concurrente, esta es la única solución aquí que funcionará.
ndm13
ConcurrentSkipListMap agrega una penalización de rendimiento innecesaria por tener el árbol como estructura de datos base, en lugar de usar HashTable, incluso cuando no necesita la funcionalidad de clasificación / navegación.
Ajeet Ganga
no lo use a ConcurrentSkipListSetmenos que quiera un SortedSet. Una operación habitual como agregar o quitar debería ser O (1) para a HashSet, pero O (log (n)) para a SortedSet.
benez
16

Como se ha señalado por esta la mejor manera de obtener un HashSet concurrencia-poder es por medio deCollections.synchronizedSet()

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

Esto funcionó para mí y no he visto a nadie realmente señalando.

EDITAR Esto es menos eficiente que la solución aprobada actualmente, como señala Eugene, ya que simplemente envuelve su conjunto en un decorador sincronizado, mientras que en ConcurrentHashMaprealidad implementa una concurrencia de bajo nivel y puede respaldar su Conjunto igual de bien. Así que gracias al Sr. Stepanenkov por dejar eso claro.

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

Nirro
fuente
16
el synchronizedSetmétodo que se acaba crea el decorador bajo Collectiona los métodos de envoltura que podrían ser thread-safe por sincronización de toda la colección. Pero ConcurrentHashMapse implementa utilizando algoritmos sin bloqueo y sincronizaciones de "bajo nivel" sin ningún bloqueo de toda la colección. Entonces, los wrapers de Collections.synchronized... son peores en entornos de subprocesos múltiples por razones de rendimiento.
Eugene Stepanenkov el
12

Puedes usar guayabas Sets.newSetFromMap(map)para conseguir uno. Java 6 también tiene ese método enjava.util.Collections

Bozho
fuente
está disponible en java.utll. Las colecciones y el conjunto de CHM suelen ser algo malo de todos modos.
bestsss
Sí, noté que se agrega en Java 6, así que lo agregué a la respuesta
Bozho,
Lo principal es que si es ThreadSafe, y realmente lo dudo.
Talha Ahmed Khan
@Talha, es seguro de rosca, sin embargo hilo de seguridad medios solos nada
bestsss
A veces significa todo. Raramente es un problema de rendimiento a menos que sea parte de un algoritmo que generalmente se implementa de manera tal que se minimice la necesidad de mapeo concurrente.
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
fuente
2
Me gusta la idea de usar Boolean.TRUE en lugar de un objeto ficticio. Es un poco más elegante. También es posible usar NULL, ya que estaría disponible en el conjunto de claves incluso si se asigna a nulo.
Martin Kersten
2
@MartinKersten fyi, ConcurrentHashMap no permite valores nulos
Lauri Lehtinen
2

¿Por qué no usar: CopyOnWriteArraySet de java.util.concurrent?

Shendor
fuente
66
Debido a que CopyOnWriteArraySet copia la colección completa en cualquier mutación de estado, lo que no siempre se desea debido al impacto en el rendimiento. Está diseñado para funcionar solo en casos especiales.
boneash