Mengapa tidak ada ConcurrentHashSet terhadap ConcurrentHashMap

537

HashSet didasarkan pada HashMap.

Jika kita melihat HashSet<E>implementasi, semuanya dikelola di bawah HashMap<E,Object>.

<E>digunakan sebagai kunci dari HashMap.

Dan kita tahu bahwa HashMapitu tidak aman. Itu sebabnya kami ada ConcurrentHashMapdi Jawa.

Berdasarkan ini, saya bingung mengapa kita tidak memiliki ConcurrentHashSet yang harus didasarkan pada ConcurrentHashMap?

Apakah ada hal lain yang saya lewatkan? Saya perlu menggunakan Setdalam lingkungan multi-utas.

Juga, Jika saya ingin membuat milik saya sendiri, ConcurrentHashSetbisakah saya mencapainya dengan hanya mengganti HashMapto ConcurrentHashMapdan membiarkan sisanya seperti apa adanya?

Talha Ahmed Khan
sumber
2
Setelah melihat API, jika saya menebak saya akan mengatakan bahwa tampaknya turun menjadi 2 faktor, (1) menghindari harus membuat kelas di Java API untuk setiap sedikit fungsionalitas yang diperlukan (2) Menyediakan kelas kenyamanan untuk benda yang lebih sering digunakan. Saya pribadi lebih suka LinkedHashMap dan LinkedHashSet karena mereka menjamin pesanan sama dengan urutan penyisipan, satu-satunya alasan menggunakan set adalah untuk menghindari duplikasi, sering saya masih ingin mempertahankan urutan penyisipan.
Ali
1
@ Ali, saya pribadi lebih suka LinkedHashMap dan LinkedHashSet Anda akan pergi jauh :)
bestsss
9
Pertanyaan yang agak lama, tetapi karena ini adalah hasil pertama di Google, mungkin berguna untuk mengetahui bahwa ConcurrentSkipListSet sudah memiliki implementasi ConcurrentHashMap. Lihat docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
Igor Rodriguez
1
Apa yang saya lihat dari sumber Java ConcurrentSkipListSetdibangun di atas ConcurrentSkipListMap, yang mengimplementasikan ConcurrentNavigableMapdan ConcurrentMap.
Talha Ahmed Khan

Jawaban:

581

Tidak ada tipe bawaan ConcurrentHashSetkarena Anda selalu dapat memperoleh satu set dari peta. Karena ada banyak jenis peta, Anda menggunakan metode untuk menghasilkan satu set dari peta yang diberikan (atau kelas peta).

Sebelum ke Java 8, Anda menghasilkan hash bersamaan yang didukung oleh peta hash bersamaan, dengan menggunakan Collections.newSetFromMap(map)

Di Jawa 8 (ditunjukkan oleh @ Matt), Anda bisa mendapatkan konkuren hash set pandangan via ConcurrentHashMap.newKeySet(). Ini sedikit lebih sederhana daripada yang lama newSetFromMapyang mengharuskan Anda untuk memasukkan objek peta kosong. Tetapi khusus untuk ConcurrentHashMap.

Bagaimanapun, desainer Java bisa saja membuat set antarmuka baru setiap kali antarmuka peta baru dibuat, tetapi pola itu tidak mungkin ditegakkan ketika pihak ketiga membuat peta mereka sendiri. Lebih baik memiliki metode statis yang memperoleh set baru; pendekatan itu selalu berhasil, bahkan ketika Anda membuat implementasi peta Anda sendiri.

Ray Toal
sumber
4
Apakah saya berhak mengatakan bahwa jika Anda membuat set dengan cara ini ConcurrentHashMap, Anda kehilangan manfaat yang akan Anda dapatkan ConcurrentHashMap?
Pacerier
19
Tidak ada untungnya rugi. newSetFromMapImplementasi ditemukan mulai pada baris 3841 di docjar.com/html/api/java/util/Collections.java.html . Itu hanya pembungkus ....
Ray Toal
4
@Andrew, saya pikir motivasi di balik menggunakan "ConcurrentSet" berasal dari bukan API tetapi lebih pada implementasi - keamanan thread tetapi tanpa kunci universal - beberapa bersamaan berbunyi misalnya.
Ustaman Sangat
5
ConcurrentSkipList memiliki banyak (ukuran) overhead dan pencarian lebih lambat.
eckes
3
berhati-hatilah saat menggunakan pendekatan ini, karena beberapa metode tidak diterapkan dengan benar. Cukup ikuti tautannya: Collections.newSetFromMapbuat a SetFromMap. misalnya SetFromMap.removeAllmetode delegasi ke KeySetView.removeAll, yang mewarisi dari ConcurrentHashMap$CollectionView.removeAll. Metode ini sangat tidak efisien dalam elemen pemindahan massal. bayangkan removeAll(Collections.emptySet())melintasi semua elemen Maptanpa melakukan apa pun. Memiliki ConcurrentHashSetyang diterapkan dengan benar akan lebih baik dalam banyak kasus.
benez
104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Topeng Sersan
sumber
79

Dengan Guava 15 Anda juga dapat menggunakan:

Set s = Sets.newConcurrentHashSet();
kichik
sumber
12
Ini selalu merupakan mimpi buruk. Jika Anda memiliki satu set atau peta yang tidak menunjukkan apakah sesuatu aman atau tidak, Anda menemukan semua jenis bahaya dan desaster terjadi dalam pemeliharaan. Saya selalu ingin jenis yang menunjukkan keamanan utas untuk koleksi (atau tidak).
Martin Kersten
11
Deskripsi metode secara harfiah "Membuat set aman-thread yang didukung oleh peta hash"
kichik
16
Seperti yang saya katakan, ada ConcurrentSet <E> yang hilang. ConcurrentHashMap hadir bersama dengan antarmuka ConcurrentMap untuk menunjukkan hal ini. Ini adalah alasan yang sama saya selalu menambahkan antarmuka ConcurrentSet ini juga.
Martin Kersten
35

Seperti yang disebutkan oleh Ray Toal , ini semudah:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
BullyWiiPlaza
sumber
1
Ini tampaknya membutuhkan Java 8. Melihat implementasi, ini juga tampaknya hanya pembungkus ConcurrentHashMap.
Mygod
20

Sepertinya Java menyediakan implementasi Set bersamaan dengan ConcurrentSkipListSet -nya . Sebuah SkipList Set hanya jenis khusus dari pelaksanaan set. Itu masih mengimplementasikan antarmuka Serializable, Cloneable, Iterable, Collection, NavigableSet, Set, SortedSet. Ini mungkin bekerja untuk Anda jika Anda hanya perlu mengatur antarmuka.

Mike Pone
sumber
12
Perhatikan bahwa ConcurrentSkipListSetelemen harusComparable
user454322
Jika Anda perlu memperluas dari Set yang bersamaan, ini adalah satu-satunya solusi di sini yang akan berfungsi.
ndm13
ConcurrentSkipListMap menambahkan penalti kinerja yang tidak perlu karena memiliki struktur data dasar, alih-alih menggunakan HashTable, bahkan ketika Anda tidak memerlukan fungsi sortasi / navigasi.
Ajeet Ganga
jangan gunakan ConcurrentSkipListSetkecuali Anda menginginkan SortedSet. Operasi biasa seperti menambah atau menghapus harus O (1) untuk a HashSet, tetapi O (log (n)) untuk a SortedSet.
benez
16

Seperti yang ditunjukkan oleh ini , cara terbaik untuk mendapatkan HashSet yang mampu melakukan konkurensi adalah dengan caraCollections.synchronizedSet()

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

Ini bekerja untuk saya dan saya belum melihat ada orang yang benar-benar menunjuk ke sana.

EDIT Ini kurang efisien daripada solusi yang saat ini disetujui, seperti yang ditunjukkan Eugene, karena itu hanya membungkus set Anda ke dekorator yang disinkronkan, sementara yang ConcurrentHashMapsebenarnya mengimplementasikan konkurensi tingkat rendah dan itu dapat mendukung Set Anda dengan baik. Jadi terima kasih kepada Tn. Stepanenkov karena menjelaskannya.

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

Nirro
sumber
16
yang synchronizedSetmetode hanya menciptakan dekorator bawah Collectionuntuk metode bungkus yang bisa benang-aman dengan sinkronisasi seluruh koleksi. Tetapi ConcurrentHashMapdiimplementasikan menggunakan algoritma non-blocking dan sinkronisasi "tingkat rendah" tanpa kunci seluruh koleksi. Jadi pembungkus dari Collections.synchronized... lebih buruk di lingkungan multi-utas untuk alasan kinerja.
Eugene Stepanenkov
12

Anda dapat menggunakan jambu biji Sets.newSetFromMap(map)untuk mendapatkannya. Java 6 juga memiliki metode itu dijava.util.Collections

Bozho
sumber
ini tersedia di java.utll.Collections dan set CHM biasanya merupakan hal yang buruk.
bestsss
ya, saya perhatikan itu ditambahkan di Java 6, jadi ditambahkan ke jawabannya
Bozho
Yang utama adalah jika itu adalah ThreadSafe, dan saya benar-benar meragukannya.
Talha Ahmed Khan
@Talha, aman dari benang, namun keamanan benang saja tidak ada artinya
bestsss
Terkadang itu berarti segalanya. Ini hampir merupakan masalah kinerja kecuali jika itu adalah bagian dari algoritma yang biasanya diimplementasikan sedemikian rupa sehingga kebutuhan untuk pemetaan bersamaan diminimalkan.
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
sumber
2
Saya suka ide untuk menggunakan Boolean.TRUE bukan objek dummy. Ini sedikit lebih elegan. Juga menggunakan NULL juga dimungkinkan karena itu akan tersedia di set kunci bahkan jika dipetakan ke nol.
Martin Kersten
2
@MartinKersten fyi, ConcurrentHashMap tidak mengizinkan nilai nol
Lauri Lehtinen
2

Mengapa tidak menggunakan: CopyOnWriteArraySet dari java.util.concurrent?

Shendor
sumber
6
Karena CopyOnWriteArraySet menyalin seluruh koleksi pada setiap mutasi negara, yang tidak selalu diinginkan karena dampak kinerja. Ini dirancang untuk bekerja hanya dalam kasus khusus.
Boneash