線程安全的問題,真的算是老生常談了。這幾天看到一個 HashSet 線程安全的騷操作,在這里分享給大家。 在本文中,我們將分享如何構造線程安全的HashSet的幾種方法。
使用ConcurrentHashMap工廠方法構造線程安全的HashSet
首先, 我們來看看_ConcurrentHashMap_暴露出來的靜態方法 -- newKeySet()
。此方法返回一個Set的實例,等同于實現了 java.util.Set 接口,而且能夠使用Set的一些常用操作,比如 add(), contains() 等。
舉個例子:
Set< Integer > threadSafeUniqueNumbers = ConcurrentHashMap.newKeySet();
threadSafeUniqueNumbers.add(23);
threadSafeUniqueNumbers.add(45);
這里返回的Set,其實有點類似于 HashSet,因為兩者都是基于Hash算法實現的,另外線程同步邏輯帶來的額外開銷也很小,因為它最終還是 ConcurrentHashMap 的一部分。
不過,這個只能在 Java 8 以上版本才可以使用,我想大部分公司應該至少 Java 8 了吧。直接拿來用就行。
現在,我們已經了解了可以用 ConcurrentHashMap#newKeySet()
構建類似于線程安全的HashSet,在 ConcurrentHashMap 其實被定義為 KeySetView
。ConcurrentHashMap 其實還有兩個實例方法可以用于構建 KeySetView, 一個是 keySet()
另外一個就是keySet(defaultValue)
, 我這里就簡寫一下了, 大家可以在IDE中直接打出來看看。
這兩個方法都可以創建KeySetView的實例,KeySetView 與 Map 是一個連接的關系。 我們每次向Map中添加新的鍵值對的時候,Set中的數據也在相應的添加,我們通過幾個例子來看看這兩種方法有哪些區別。
KeySet() 方法
keySet() 方法 和 keySet(defaultValue) ,最大的區別就是不能直接往Set中添加數據。直接添加的話,會拋出 UnsupportedOperationException 異常,源碼中的定義如下。
public KeySetView< K,V > keySet() {
KeySetView< K,V > ks;
if ((ks = keySet) != null) return ks;
return keySet = new KeySetView< K,V >(this, null);
}
// add
public boolean add(K e) {
V v;
if ((v = value) == null)
throw new UnsupportedOperationException();
return map.putVal(e, v, true) == null;
}
所以我們只能通過如下的方式使用。
ConcurrentHashMap< Integer,String > numbersMap = new ConcurrentHashMap< >();
Set< Integer > numbersSet = numbersMap.keySet();
numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");
System.out.println("Map before remove: "+ numbersMap);
System.out.println("Set before remove: "+ numbersSet);
numbersSet.remove(2);
System.out.println("Set after remove: "+ numbersSet);
System.out.println("Map after remove: "+ numbersMap);
輸出結果如下。
Map before remove: {1=One, 2=Two, 3=Three}
Set before remove: [1, 2, 3]
Set after remove: [1, 3]
Map after remove: {1=One, 3=Three}
KeySet(defaultValue) 方法
keySet(defaultValue) ,由于有設置默認的value,可以在添加的時候不會報錯,JDK 源碼縱定義如下:
public KeySetView< K,V > keySet(V mappedValue) {
if (mappedValue == null)
throw new NullPointerException();
return new KeySetView< K,V >(this, mappedValue);
}
所以我們可以通過如下的方式使用。
ConcurrentHashMap< Integer,String > numbersMap = new ConcurrentHashMap< >();
Set< Integer > numbersSet = numbersMap.keySet("SET-ENTRY");
numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");
System.out.println("Map before add: "+ numbersMap);
System.out.println("Set before add: "+ numbersSet);
numbersSet.addAll(asList(4,5));
System.out.println("Map after add: "+ numbersMap);
System.out.println("Set after add: "+ numbersSet);
輸出結果如下:
Map before add: {1=One, 2=Two, 3=Three}
Set before add: [1, 2, 3]
Map after add: {1=One, 2=Two, 3=Three, 4=SET-ENTRY, 5=SET-ENTRY}
Set after add: [1, 2, 3, 4, 5]
使用Collections的來創建線程安全的 Set
java.util.Collections
中有一個線程同步的方法可以用于創建,示例代碼如下。
Set< Integer > syncNumbers = Collections.synchronizedSet(new HashSet< >());
syncNumbers.add(1);
這個方法的性能并沒有ConcurrentHashMap的那個效率高,由于使用了同步鎖,增加了一些額外的開銷。
使用CopyOnWriteArraySet構建線程安全的 Set
用CopyOnWriteArraySet 創建線程安全的 set 也是非常簡單的。示例代碼如下
Set< Integer > copyOnArraySet = new CopyOnWriteArraySet< >();
copyOnArraySet.add(1);
這個方法從性能的角度上來看,也不是很理想,CopyOnWriteArraySet 背后的實現是CopyOnWriteArrayList, 最終使用了數組來存儲數據,也就意味著 contains() 或者 remove() 操作,具有 O(n) 的復雜度,而使用HashMap 復雜度為 O(1) 。
建議使用此實現時,設置大小通常保持較小,只讀操作占大多數。
總結
在本文中,我們看到了不同的創建線程安全的Set的方式,也比較了他們之間的差異性。 所以大家以后使用的時候,可以考慮使用ConcurrentHashMap創建的Set。
-
接口
+關注
關注
33文章
8526瀏覽量
150862 -
數據
+關注
關注
8文章
6909瀏覽量
88849 -
SET
+關注
關注
0文章
17瀏覽量
7937 -
線程
+關注
關注
0文章
504瀏覽量
19653 -
Hash算法
+關注
關注
0文章
43瀏覽量
7380
發布評論請先 登錄
相關推薦
評論