如何保證HashSet線程安全?
大家好,我是指北君。
線程安全的問題,真的算是老生常談了。這幾天看到一個(gè) HashSet 線程安全的騷操作,在這里分享給大家。 在本文中,我們將分享如何構(gòu)造線程安全的HashSet的幾種方法。
使用ConcurrentHashMap工廠方法構(gòu)造線程安全的HashSet
首先, 我們來看看_ConcurrentHashMap_暴露出來的靜態(tài)方法 -- newKeySet()。此方法返回一個(gè)Set的實(shí)例,等同于實(shí)現(xiàn)了 java.util.Set 接口,而且能夠使用Set的一些常用操作,比如 add(), contains() 等。
舉個(gè)例子:
這里返回的Set,其實(shí)有點(diǎn)類似于 HashSet,因?yàn)閮烧叨际腔贖ash算法實(shí)現(xiàn)的,另外線程同步邏輯帶來的額外開銷也很小,因?yàn)樗罱K還是 ConcurrentHashMap 的一部分。
不過,這個(gè)只能在 Java 8 以上版本才可以使用,我想大部分公司應(yīng)該至少 Java 8 了吧。直接拿來用就行。
現(xiàn)在,我們已經(jīng)了解了可以用 ConcurrentHashMap#newKeySet()構(gòu)建類似于線程安全的HashSet,在 ConcurrentHashMap 其實(shí)被定義為 KeySetView。ConcurrentHashMap 其實(shí)還有兩個(gè)實(shí)例方法可以用于構(gòu)建 KeySetView, 一個(gè)是 keySet() 另外一個(gè)就是keySet(defaultValue), 我這里就簡寫一下了, 大家可以在IDE中直接打出來看看。
這兩個(gè)方法都可以創(chuàng)建KeySetView的實(shí)例,KeySetView 與 Map 是一個(gè)連接的關(guān)系。 我們每次向Map中添加新的鍵值對(duì)的時(shí)候,Set中的數(shù)據(jù)也在相應(yīng)的添加,我們通過幾個(gè)例子來看看這兩種方法有哪些區(qū)別。
KeySet() 方法
keySet() 方法 和 keySet(defaultValue) ,最大的區(qū)別就是不能直接往Set中添加數(shù)據(jù)。直接添加的話,會(huì)拋出 UnsupportedOperationException 異常,源碼中的定義如下。
所以我們只能通過如下的方式使用。
輸出結(jié)果如下。
KeySet(defaultValue) 方法
keySet(defaultValue) ,由于有設(shè)置默認(rèn)的value,可以在添加的時(shí)候不會(huì)報(bào)錯(cuò),JDK 源碼縱定義如下:
所以我們可以通過如下的方式使用。
輸出結(jié)果如下:
使用Collections的來創(chuàng)建線程安全的 Set
java.util.Collections 中有一個(gè)線程同步的方法可以用于創(chuàng)建,示例代碼如下。
這個(gè)方法的性能并沒有ConcurrentHashMap的那個(gè)效率高,由于使用了同步鎖,增加了一些額外的開銷。
使用CopyOnWriteArraySet構(gòu)建線程安全的 Set
用CopyOnWriteArraySet 創(chuàng)建線程安全的 set 也是非常簡單的。示例代碼如下
這個(gè)方法從性能的角度上來看,也不是很理想,CopyOnWriteArraySet 背后的實(shí)現(xiàn)是CopyOnWriteArrayList, 最終使用了數(shù)組來存儲(chǔ)數(shù)據(jù),也就意味著 contains() 或者 remove() 操作,具有 O(n) 的復(fù)雜度,而使用HashMap 復(fù)雜度為 O(1) 。
建議使用此實(shí)現(xiàn)時(shí),設(shè)置大小通常保持較小,只讀操作占大多數(shù)。
總結(jié)
在本文中,我們看到了不同的創(chuàng)建線程安全的Set的方式,也比較了他們之間的差異性。 所以大家以后使用的時(shí)候,可以考慮使用ConcurrentHashMap創(chuàng)建的Set。