Tại sao không có ConcurrencyHashset chống lại ConcảnHashMap


537

Hashset dựa trên HashMap.

Nếu chúng ta nhìn vào HashSet<E>việc thực hiện, mọi thứ đều được quản lý theo HashMap<E,Object>.

<E>được sử dụng như là một chìa khóa của HashMap.

Và chúng tôi biết rằng đó HashMapkhông phải là chủ đề an toàn. Đó là lý do tại sao chúng ta có ConcurrentHashMaptrong Java.

Dựa trên điều này, tôi bối rối rằng tại sao chúng ta không có một concảnHashset nên dựa trên ConcurrentHashMap?

Có điều gì khác mà tôi đang thiếu? Tôi cần sử dụng Settrong một môi trường đa luồng.

Ngoài ra, Nếu tôi muốn tạo của riêng tôi ConcurrentHashSettôi có thể đạt được điều đó bằng cách thay thế HashMapđể ConcurrentHashMapvà để lại phần còn lại như là?


2
Sau khi xem API, nếu tôi đoán tôi sẽ nói rằng nó dường như có đến 2 yếu tố, (1) tránh phải tạo một lớp trong API Java cho mỗi chút chức năng cần thiết (2) Cung cấp các lớp tiện lợi cho đối tượng sử dụng thường xuyên hơn. Cá nhân tôi thích LinkedHashMap và LinkedHashset vì chúng đảm bảo thứ tự giống như thứ tự chèn, lý do duy nhất để sử dụng một bộ là để tránh trùng lặp, thường thì tôi vẫn muốn duy trì thứ tự chèn.
Ali

1
@Ali, cá nhân tôi thích LinkedHashMap và LinkedHashset bạn sẽ đi xa :)
bestsss

9
Một câu hỏi hơi cũ, nhưng vì đây là kết quả đầu tiên trong Google, có thể hữu ích khi biết rằng ConcurrencySkipListSet đã có triển khai ConcurrencyHashMap. Xem docs.oracle.com/javase/7/docs/api/java/util/concản/iêu
Igor Rodriguez

1
Những gì tôi thấy từ nguồn Java ConcurrentSkipListSetđược xây dựng dựa trên ConcurrentSkipListMap, nó thực hiện ConcurrentNavigableMapConcurrentMap.
Talha Ahmed Khan

Câu trả lời:


581

Không có loại tích hợp nào ConcurrentHashSetvì bạn luôn có thể lấy được một bộ từ bản đồ. Vì có nhiều loại bản đồ, bạn sử dụng một phương pháp để tạo một bộ từ một bản đồ nhất định (hoặc lớp bản đồ).

Trước Java 8, bạn tạo một bộ băm đồng thời được hỗ trợ bởi bản đồ băm đồng thời, bằng cách sử dụng Collections.newSetFromMap(map)

Trong Java 8 (được chỉ ra bởi @Matt), bạn có thể có được chế độ xem băm đồng thời thông qua ConcurrentHashMap.newKeySet(). Điều này đơn giản hơn một chút so với cái cũ newSetFromMapđòi hỏi bạn phải vượt qua trong một đối tượng bản đồ trống. Nhưng nó là cụ thể để ConcurrentHashMap.

Dù sao, các nhà thiết kế Java có thể đã tạo ra một giao diện thiết lập mới mỗi khi giao diện bản đồ mới được tạo, nhưng mẫu đó sẽ không thể thực thi khi các bên thứ ba tạo bản đồ của riêng họ. Nó là tốt hơn để có các phương thức tĩnh mà dẫn xuất các bộ mới; Cách tiếp cận đó luôn hoạt động, ngay cả khi bạn tạo các triển khai bản đồ của riêng mình.


4
Tôi có đúng không khi nói rằng nếu bạn tạo tập hợp theo cách này ConcurrentHashMap, bạn sẽ mất những lợi ích bạn nhận được ConcurrentHashMap?
Pacerier

19
Không có lợi ích để mất. newSetFromMapViệc triển khai được tìm thấy bắt đầu từ dòng 3841 trong docjar.com/html/api/java/util/Collections.java.html . Nó chỉ là một trình bao bọc ....
Ray Toal

4
@Andrew, tôi nghĩ rằng động lực đằng sau việc sử dụng "Bộ đồng thời" bắt nguồn từ không phải API mà là việc triển khai - an toàn luồng nhưng không có khóa chung - ví dụ nhiều lần đọc đồng thời .
Ustaman Sangat

5
Đồng thờiSkipList có rất nhiều (kích thước) chi phí và việc tra cứu chậm hơn.
eckes

3
Cẩn thận khi sử dụng phương pháp này, vì một số phương pháp không được thực hiện đúng. Chỉ cần làm theo các liên kết: Collections.newSetFromMaptạo ra một SetFromMap. ví dụ: SetFromMap.removeAllphương thức ủy nhiệm cho KeySetView.removeAll, kế thừa từ ConcurrentHashMap$CollectionView.removeAll. Phương pháp này rất kém hiệu quả trong việc loại bỏ hàng loạt các yếu tố. tưởng tượng removeAll(Collections.emptySet())đi qua tất cả các yếu tố trong Mapmà không làm gì cả. Có một ConcurrentHashSetđiều đó được thực hiện đúng sẽ tốt hơn trong hầu hết các trường hợp.
benez


79

Với Guava 15 bạn cũng có thể sử dụng đơn giản:

Set s = Sets.newConcurrentHashSet();

12
Đây luôn là một cơn ác mộng. Nếu bạn có một bộ hoặc một bản đồ không cho biết liệu thứ gì đó có an toàn hay không, bạn sẽ tìm thấy tất cả các mối nguy hiểm và trình giải mã xảy ra trong bảo trì. Tôi luôn muốn có một loại chỉ ra sự an toàn của luồng cho các bộ sưu tập (hoặc không).
Martin Kersten

11
Mô tả phương thức có nghĩa đen là "Tạo một bộ an toàn luồng được hỗ trợ bởi bản đồ băm"
kichik

16
Như tôi đã nói, có một thiếu đồng thời <E>. ConcảnHashMap đi kèm với giao diện Concản đồ để chỉ ra điều này. Đây là cùng một lý do tôi luôn luôn thêm giao diện này cùng lúc.
Martin Kersten

35

Giống như Ray Toal đã đề cập, nó dễ như:

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

1
Điều này dường như đòi hỏi Java 8. Nhìn vào việc thực hiện, đây dường như cũng chỉ là một trình bao bọc của ConcurrentHashMap.
Mygod

20

Dường như Java cung cấp một triển khai Set đồng thời với ConcurrencySkipListSet của nó . Một SkipList Set chỉ là một loại đặc biệt của thực hiện quy định. Nó vẫn thực hiện các giao diện serializable, Clonizable, Iterable, Collection, Navigableset, Set, Sortedset. Điều này có thể phù hợp với bạn nếu bạn chỉ cần giao diện Set.


12
Lưu ý rằng ConcurrentSkipListSetcác yếu tố nên làComparable
user454322

Nếu bạn cần gia hạn từ Tập hợp đồng thời, đây là giải pháp duy nhất ở đây sẽ hoạt động.
ndm13

ConcảnSkipListMap thêm hình phạt hiệu suất không cần thiết khi có cây làm cấu trúc dữ liệu cơ sở, thay vì sử dụng HashTable, ngay cả khi bạn không cần chức năng sắp xếp / điều hướng.
Ajeet Ganga

không sử dụng ConcurrentSkipListSet, trừ khi bạn muốn có một SortedSet. Một hoạt động thông thường như thêm hoặc loại bỏ phải là O (1) cho a HashSet, nhưng O (log (n)) cho a SortedSet.
benez

16

Như được chỉ ra bởi cách này tốt nhất để có được Hashset có khả năng tương tranh là bằng cáchCollections.synchronizedSet()

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

Điều này làm việc cho tôi và tôi chưa thấy ai thực sự chỉ vào nó.

EDIT Điều này kém hiệu quả hơn so với giải pháp hiện tại, như Eugene chỉ ra, vì nó chỉ gói thiết bị của bạn vào một trình trang trí được đồng bộ hóa, trong khi ConcurrentHashMapthực tế thực hiện đồng thời mức độ thấp và nó có thể hỗ trợ Set của bạn tốt như vậy. Vì vậy, cảm ơn ông Stepanenkov vì đã làm rõ điều đó.

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


16
các synchronizedSetphương pháp chỉ tạo ra các trang trí dưới Collectioncác phương pháp bọc đó có thể là thread-an toàn bằng cách đồng bộ hóa toàn bộ bộ sưu tập. Nhưng ConcurrentHashMapđược triển khai bằng các thuật toán không chặn và đồng bộ hóa "cấp thấp" mà không có bất kỳ khóa nào của toàn bộ bộ sưu tập. Vì vậy, các bọc từ Collections.synchronized... là tồi tệ hơn trong môi trường đa luồng vì lý do hiệu suất.
Eugene Stepanenkov

12

Bạn có thể sử dụng ổi Sets.newSetFromMap(map)để có được một. Java 6 cũng có phương thức đó trongjava.util.Collections


nó có sẵn trong java.utll.Collections và bộ CHM thường là một điều xấu.
bestsss

vâng, tôi nhận thấy nó được thêm vào trong Java 6, vì vậy đã thêm nó vào câu trả lời
Bozho

Chính điều này là nếu đó là ThreadSafe, và tôi thực sự nghi ngờ điều đó.
Talha Ahmed Khan

@Talha, đó là chủ đề an toàn, tuy nhiên an toàn chủ đề chỉ có nghĩa là không có gì
tốt nhất là vào

Đôi khi nó có nghĩa là tất cả mọi thứ. Đây là một vấn đề hiệu năng trừ khi nó là một phần của thuật toán thường được thực hiện theo cách mà nhu cầu lập bản đồ đồng thời được giảm thiểu.
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
Tôi thích ý tưởng sử dụng Boolean.TRUE thay vì một đối tượng giả. Nó là một chút thanh lịch hơn. Cũng có thể sử dụng NULL vì nó sẽ có sẵn trong bộ khóa ngay cả khi được ánh xạ thành null.
Martin Kersten

2
@MartinKersten fyi, Đồng thờiHashMap không cho phép giá trị null
Lauri Lehtinen

2

Tại sao không sử dụng: CopyOnWriteArraySet từ java.util.conc hiện?


6
Bởi vì CopyOnWriteArraySet sao chép toàn bộ bộ sưu tập trên bất kỳ đột biến trạng thái nào, điều này không phải lúc nào cũng muốn do tác động hiệu suất. Nó được thiết kế để chỉ hoạt động trong trường hợp đặc biệt.
boneash
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.