Por que não há ConcurrentHashSet contra ConcurrentHashMap

537

O HashSet é baseado no HashMap.

Se observarmos a HashSet<E>implementação, tudo será gerenciado HashMap<E,Object>.

<E>é usado como uma chave de HashMap.

E sabemos que isso HashMapnão é seguro para threads. É por isso que temos ConcurrentHashMapem Java.

Com base nisso, estou confuso de que por que não temos um ConcurrentHashSet que deve ser baseado no ConcurrentHashMap?

Falta mais alguma coisa? Eu preciso usar Setem um ambiente multiencadeado.

Além disso, se eu quiser criar o meu próprio, ConcurrentHashSetposso consegui-lo apenas substituindo o HashMappara ConcurrentHashMape deixando o resto como está?

Talha Ahmed Khan
fonte
2
Depois de analisar a API, se eu adivinhar, diria que ela parece se resumir a 2 fatores, (1) evitar a necessidade de criar uma classe na API Java para todas as funcionalidades necessárias (2) fornecer classes de conveniência para objetos usados ​​com mais frequência. Pessoalmente, prefiro o LinkedHashMap e o LinkedHashSet, pois eles garantem que o pedido é o mesmo que o pedido de inserção. O único motivo para usar um conjunto é evitar a duplicação. Muitas vezes, ainda quero manter o pedido de inserção.
Ali
1
@Ali, eu pessoalmente prefiro LinkedHashMap e LinkedHashSet você vai longe :)
bestsss
9
Uma pergunta um pouco antiga, mas como é o primeiro resultado no Google, pode ser útil saber que o ConcurrentSkipListSet já possui a implementação do ConcurrentHashMap. Veja docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Igor Rodriguez
1
O que eu vi da fonte Java ConcurrentSkipListSeté construído ConcurrentSkipListMap, o que implementa ConcurrentNavigableMape ConcurrentMap.
precisa

Respostas:

581

Não existe um tipo incorporado ConcurrentHashSetporque você sempre pode derivar um conjunto de um mapa. Como existem muitos tipos de mapas, você usa um método para produzir um conjunto a partir de um determinado mapa (ou classe de mapa).

Antes do Java 8, você produz um conjunto de hash simultâneo apoiado por um mapa de hash simultâneo, usando Collections.newSetFromMap(map)

No Java 8 (apontado por @Matt), você pode obter uma visualização simultânea de conjunto de hash via ConcurrentHashMap.newKeySet(). Isso é um pouco mais simples que o antigo, newSetFromMapque exigia que você passasse um objeto de mapa vazio. Mas é específico para ConcurrentHashMap.

De qualquer forma, os designers de Java poderiam ter criado uma nova interface de conjunto toda vez que uma nova interface de mapa fosse criada, mas esse padrão seria impossível de aplicar quando terceiros criam seus próprios mapas. É melhor ter os métodos estáticos que derivam novos conjuntos; essa abordagem sempre funciona, mesmo quando você cria suas próprias implementações de mapas.

Ray Toal
fonte
4
Estou certo em dizer que, se você criar o cenário dessa maneira ConcurrentHashMap, perderá os benefícios dos quais obteria ConcurrentHashMap?
Pacerier
19
Não há benefícios a perder. newSetFromMapA implementação de é encontrada a partir da linha 3841 em docjar.com/html/api/java/util/Collections.java.html . É apenas um wrapper ....
Ray Toal
4
@ Andrew, acho que a motivação por trás do uso de um "ConcurrentSet" decorre não da API, mas da implementação - segurança de thread, mas sem um bloqueio universal - várias leituras simultâneas, por exemplo.
Ustaman Sangat 13/09/12
5
ConcurrentSkipList tem muita sobrecarga (tamanho) e as pesquisas são mais lentas.
Eckes
3
tenha cuidado ao usar essa abordagem, pois alguns métodos não foram implementados corretamente. Basta seguir os links: Collections.newSetFromMapcria um SetFromMap. por exemplo, o SetFromMap.removeAllmétodo delega para o KeySetView.removeAll, que herda de ConcurrentHashMap$CollectionView.removeAll. Este método é altamente ineficiente para remover elementos em massa. imagine removeAll(Collections.emptySet())atravessar todos os elementos no Mapsem fazer nada. Ter um ConcurrentHashSetque seja implementado corretamente será melhor na maioria dos casos.
Benez
104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Serge Mask
fonte
79

Com o Guava 15 você também pode simplesmente usar:

Set s = Sets.newConcurrentHashSet();
kichik
fonte
12
Isso é sempre um pesadelo. Se você possui um conjunto ou um mapa que não indica se algo é ou não seguro para threads, você encontra todos os tipos de perigos e desastres que ocorrem em manutenção. Eu sempre desejaria um tipo que indique segurança de threads para coleções (ou não).
Martin Kersten
11
A descrição do método é literalmente "Cria um conjunto thread-safe apoiada por um mapa de hash"
Kichik
16
Como eu disse, há um ConcurrentSet <E> ausente. ConcurrentHashMap vem junto com uma interface ConcurrentMap para indicar isso. Esse é o mesmo motivo pelo qual sempre adiciono essa interface ConcurrentSet também.
Martin Kersten
35

Como Ray Toal mencionou, é tão fácil quanto:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
BullyWiiPlaza
fonte
1
Isso parece exigir o Java 8. Observando a implementação, isso também parece ser apenas um invólucro ConcurrentHashMap.
Mygod
20

Parece que o Java fornece uma implementação simultânea do conjunto com seu ConcurrentSkipListSet . Um conjunto SkipList é apenas um tipo especial de implementação de conjunto. Ele ainda implementa as interfaces Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Isso pode funcionar para você se você precisar apenas da interface Set.

Mike Pone
fonte
12
Observe que ConcurrentSkipListSetos elementos devem serComparable
user454322
Se você precisar estender a partir de um conjunto simultâneo, esta é a única solução aqui que funcionará.
Ndm13
ConcurrentSkipListMap adiciona uma penalidade de desempenho desnecessária por ter a árvore como estrutura de dados base, em vez de usar o HashTable, mesmo quando você não precisa da funcionalidade de classificação / navegação.
Ajeet ganga
não use a ConcurrentSkipListSetmenos que você queira SortedSet. Uma operação usual como adicionar ou remover deve ser O (1) para a HashSet, mas O (log (n)) para a SortedSet.
219 benez
16

Como apontado por isso a melhor maneira de obter um HashSet simultaneidade capaz é por meio deCollections.synchronizedSet()

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

Isso funcionou para mim e eu não vi ninguém realmente apontando para isso.

EDIT Isso é menos eficiente do que a solução atualmente aprovada, como Eugene aponta, uma vez que apenas envolve seu conjunto em um decorador sincronizado, enquanto um ConcurrentHashMapimplementa a simultaneidade de baixo nível e pode fazer o seu conjunto da mesma maneira. Então, obrigado ao Sr. Stepanenkov por deixar isso claro.

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

Nirro
fonte
16
o synchronizedSetmétodo apenas cria o decorador em baixo Collectionpara agrupar métodos que poderiam ser seguros para threads pela sincronização de toda a coleção. Mas ConcurrentHashMapé implementado usando algoritmos sem bloqueio e sincronizações de "baixo nível" sem bloqueios de toda a coleção. Portanto, os invólucros de Collections.synchronized... são piores em ambientes com vários threads por motivos de desempenho.
Eugene Stepanenkov 01/01
12

Você pode usar goiabas Sets.newSetFromMap(map)para obter uma. O Java 6 também possui esse método emjava.util.Collections

Bozho
fonte
está disponível em java.utll.Collections e conjunto de CHM geralmente é uma coisa ruim de qualquer maneira.
bestsss 9/08/11
sim, eu notei que é adicionado em Java 6, então acrescentou que para a resposta
Bozho
O principal é que, se é ThreadSafe, e eu realmente duvido disso.
Talha Ahmed Khan
@Talha, é o segmento de seguros, no entanto fio de segurança por si só não significa nada
bestsss
Às vezes significa tudo. Isso representa um problema de desempenho, a menos que faça parte de um algoritmo que geralmente é implementado de maneira a minimizar a necessidade de mapeamento simultâneo.
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;
   }
}
MD. Mohiuddin Ahmed
fonte
2
Eu gosto da ideia de usar Boolean.TRUE em vez de um objeto fictício. É um pouco mais elegante. Também é possível usar NULL, pois ele estaria disponível no conjunto de chaves, mesmo que mapeado para nulo.
Martin Kersten
2
@MartinKersten fyi, ConcurrentHashMap não permite valores nulos #
Lauri Lehtinen
2

Por que não usar: CopyOnWriteArraySet de java.util.concurrent?

Shendor
fonte
6
Porque CopyOnWriteArraySet copia a coleção inteira em qualquer mutação de estado, que nem sempre é desejada devido ao impacto no desempenho. Ele foi projetado para funcionar apenas em casos especiais.
boneash