threadSafeUniqueNumbers = ConcurrentHashM" />
0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

不同創(chuàng)建線程安全Set的方式

科技綠洲 ? 來源:了不起 ? 作者:了不起 ? 2023-09-25 14:20 ? 次閱讀

線程安全的問題,真的算是老生常談了。這幾天看到一個(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< Integer > threadSafeUniqueNumbers = ConcurrentHashMap.newKeySet();
threadSafeUniqueNumbers.add(23);
threadSafeUniqueNumbers.add(45);

這里返回的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), 我這里就簡(jiǎn)寫一下了, 大家可以在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 異常,源碼中的定義如下。

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);

輸出結(jié)果如下。

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) ,由于有設(shè)置默認(rèn)的value,可以在添加的時(shí)候不會(huì)報(bào)錯(cuò),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);

輸出結(jié)果如下:

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的來創(chuàng)建線程安全的 Set

java.util.Collections 中有一個(gè)線程同步的方法可以用于創(chuàng)建,示例代碼如下。

Set< Integer > syncNumbers = Collections.synchronizedSet(new HashSet<  >());
syncNumbers.add(1);

這個(gè)方法的性能并沒有ConcurrentHashMap的那個(gè)效率高,由于使用了同步鎖,增加了一些額外的開銷。

使用CopyOnWriteArraySet構(gòu)建線程安全的 Set

用CopyOnWriteArraySet 創(chuàng)建線程安全的 set 也是非常簡(jiǎn)單的。示例代碼如下

Set< Integer > copyOnArraySet = new CopyOnWriteArraySet<  >();
copyOnArraySet.add(1);

這個(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。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8598

    瀏覽量

    151156
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    7030

    瀏覽量

    89034
  • SET
    SET
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    7952
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    19683
  • Hash算法
    +關(guān)注

    關(guān)注

    0

    文章

    43

    瀏覽量

    7383
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux下的線程安全是什么

    Linux下的線程安全原文結(jié)構(gòu)有點(diǎn)亂線程安全:多個(gè)執(zhí)行流對(duì)臨界資源進(jìn)行爭(zhēng)搶訪問,而不會(huì)造成數(shù)據(jù)二義性和邏輯混亂,成這段代碼的過程是線程
    發(fā)表于 07-01 13:34

    什么是線程安全?如何去實(shí)現(xiàn)線程安全?

    什么是線程安全?如何去實(shí)現(xiàn)線程安全?互斥實(shí)現(xiàn)的技術(shù)是什么?有哪些注意事項(xiàng)?同步實(shí)現(xiàn)的技術(shù)是什么?其操作流程有哪些?
    發(fā)表于 07-23 09:57

    初學(xué)RT-thread線程動(dòng)態(tài)創(chuàng)建

    RT-thread初學(xué)線程動(dòng)態(tài)創(chuàng)建線程靜態(tài)創(chuàng)建線程鉤子函數(shù)定時(shí)器獲取系統(tǒng)時(shí)間動(dòng)態(tài)創(chuàng)建定時(shí)器靜態(tài)
    發(fā)表于 02-24 07:32

    在RT-Thread系統(tǒng)中創(chuàng)建線程有哪幾種方式

    概述創(chuàng)建線程三要素:1.線程棧2.線程控制塊3.線程主體函數(shù)在RTT中線程
    發(fā)表于 05-07 14:14

    線程和進(jìn)程的區(qū)別和聯(lián)系,線程和進(jìn)程通信方式

    摘要:進(jìn)程和線程都是計(jì)算里的兩項(xiàng)執(zhí)行活動(dòng),各有特色和優(yōu)勢(shì)。下面就來介紹線程和進(jìn)程之間的區(qū)別聯(lián)系以及通信方式
    發(fā)表于 12-08 14:12 ?1.3w次閱讀

    線程的實(shí)現(xiàn)方式,四線程和八線程的區(qū)別介紹

    摘要:線程是程序執(zhí)行流的最小單元。四線程和八線程線程的兩種表現(xiàn)形式,下面來看看它們之間的區(qū)別以及線程的實(shí)現(xiàn)
    發(fā)表于 12-08 14:31 ?1.2w次閱讀

    linux下多線程創(chuàng)建與等待詳解

    的時(shí)候,線程就立刻被結(jié)束。而同步終結(jié)則不會(huì)立刻終結(jié),它會(huì)繼續(xù)運(yùn)行,直到到達(dá)下一個(gè)結(jié)束點(diǎn)(cancellation point)。當(dāng)一個(gè)線程被按照默認(rèn)的創(chuàng)建方式
    發(fā)表于 04-02 14:48 ?322次閱讀

    python創(chuàng)建線程的兩種方法

    1. 用函數(shù)創(chuàng)建線程 在Python3中,Python提供了一個(gè)內(nèi)置模塊 threading.Thread ,可以很方便地讓我們創(chuàng)建線程。 threading.Thread() 一
    的頭像 發(fā)表于 03-15 16:47 ?5308次閱讀

    python創(chuàng)建線程池的兩種方法

    在使用多線程處理任務(wù)時(shí)也不是線程越多越好,由于在切換線程的時(shí)候,需要切換上下文環(huán)境,依然會(huì)造成cpu的大量開銷。為解決這個(gè)問題,線程池的概念被提出來了。預(yù)先
    的頭像 發(fā)表于 03-16 16:15 ?5982次閱讀

    如何理解線程安全?

    本次分享線程安全的基礎(chǔ)知識(shí)。
    的頭像 發(fā)表于 05-08 15:03 ?858次閱讀
    如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    什么是線程安全?如何理解線程安全

    在多線程編程中,線程安全是必須要考慮的因素。
    的頭像 發(fā)表于 05-30 14:33 ?2078次閱讀
    什么是<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?如何理解<b class='flag-5'>線程</b><b class='flag-5'>安全</b>?

    線程安全怎么辦

    線程安全一直是多線程開發(fā)中需要注意的地方,可以說,并發(fā)安全保證了所有的數(shù)據(jù)都安全。 1 線程
    的頭像 發(fā)表于 10-10 15:00 ?369次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>安全</b>怎么辦

    線程池的創(chuàng)建方式有幾種

    的開銷。線程池的創(chuàng)建方式有多種,下面將詳細(xì)介紹幾種常用的線程創(chuàng)建方式。 手動(dòng)
    的頭像 發(fā)表于 12-04 16:52 ?867次閱讀

    redis多線程還能保證線程安全

    Redis是一種使用C語言編寫的高性能鍵值存儲(chǔ)系統(tǒng),它是單線程的,因?yàn)槭褂昧硕嗦窂?fù)用的方式來處理并發(fā)請(qǐng)求。這樣的實(shí)現(xiàn)方式帶來了很好的性能,但同時(shí)也引發(fā)了一些線程
    的頭像 發(fā)表于 12-05 10:28 ?1813次閱讀

    java實(shí)現(xiàn)多線程的幾種方式

    的CompletableFuture 一、繼承Thread類 繼承Thread類是實(shí)現(xiàn)多線程的最基本方式,只需創(chuàng)建一個(gè)類并繼承Thread類,重寫run()方法即可。 ``
    的頭像 發(fā)表于 03-14 16:55 ?709次閱讀