0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

不同创建线程安全Set的方式

科技绿洲 来源:了不起 作者:了不起 2023-09-25 14:20 次阅读

线程安全的问题,真的算是老生常谈了。这几天看到一个 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

    文章

    8577

    浏览量

    151023
  • 数据
    +关注

    关注

    8

    文章

    7003

    浏览量

    88944
  • SET
    SET
    +关注

    关注

    0

    文章

    17

    浏览量

    7949
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19675
  • Hash算法
    +关注

    关注

    0

    文章

    43

    浏览量

    7382
收藏 人收藏

    评论

    相关推荐

    Linux下的线程安全是什么

    Linux下的线程安全原文结构有点乱线程安全:多个执行流对临界资源进行争抢访问,而不会造成数据二义性和逻辑混乱,成这段代码的过程是线程
    发表于 07-01 13:34

    什么是线程安全?如何去实现线程安全

    什么是线程安全?如何去实现线程安全?互斥实现的技术是什么?有哪些注意事项?同步实现的技术是什么?其操作流程有哪些?
    发表于 07-23 09:57

    初学RT-thread线程动态创建

    RT-thread初学线程动态创建线程静态创建线程钩子函数定时器获取系统时间动态创建定时器静态
    发表于 02-24 07:32

    在RT-Thread系统中创建线程有哪几种方式

    概述创建线程三要素:1.线程栈2.线程控制块3.线程主体函数在RTT中线程
    发表于 05-07 14:14

    线程和进程的区别和联系,线程和进程通信方式

    摘要:进程和线程都是计算里的两项执行活动,各有特色和优势。下面就来介绍线程和进程之间的区别联系以及通信方式
    发表于 12-08 14:12 1.3w次阅读

    线程的实现方式,四线程和八线程的区别介绍

    摘要:线程是程序执行流的最小单元。四线程和八线程线程的两种表现形式,下面来看看它们之间的区别以及线程的实现
    发表于 12-08 14:31 1.2w次阅读

    linux下多线程创建与等待详解

    的时候,线程就立刻被结束。而同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式
    发表于 04-02 14:48 321次阅读

    python创建线程的两种方法

    1. 用函数创建线程 在Python3中,Python提供了一个内置模块 threading.Thread ,可以很方便地让我们创建线程。 threading.Thread() 一
    的头像 发表于 03-15 16:47 5298次阅读

    python创建线程池的两种方法

    在使用多线程处理任务时也不是线程越多越好,由于在切换线程的时候,需要切换上下文环境,依然会造成cpu的大量开销。为解决这个问题,线程池的概念被提出来了。预先
    的头像 发表于 03-16 16:15 5976次阅读

    如何理解线程安全

    本次分享线程安全的基础知识。
    的头像 发表于 05-08 15:03 853次阅读
    如何理解<b class='flag-5'>线程</b><b class='flag-5'>安全</b>?

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

    在多线程编程中,线程安全是必须要考虑的因素。
    的头像 发表于 05-30 14:33 2068次阅读
    什么是<b class='flag-5'>线程</b><b class='flag-5'>安全</b>?如何理解<b class='flag-5'>线程</b><b class='flag-5'>安全</b>?

    线程安全怎么办

    线程安全一直是多线程开发中需要注意的地方,可以说,并发安全保证了所有的数据都安全。 1 线程
    的头像 发表于 10-10 15:00 365次阅读
    <b class='flag-5'>线程</b><b class='flag-5'>安全</b>怎么办

    线程池的创建方式有几种

    的开销。线程池的创建方式有多种,下面将详细介绍几种常用的线程创建方式。 手动
    的头像 发表于 12-04 16:52 856次阅读

    redis多线程还能保证线程安全

    Redis是一种使用C语言编写的高性能键值存储系统,它是单线程的,因为使用了多路复用的方式来处理并发请求。这样的实现方式带来了很好的性能,但同时也引发了一些线程
    的头像 发表于 12-05 10:28 1797次阅读

    java实现多线程的几种方式

    的CompletableFuture 一、继承Thread类 继承Thread类是实现多线程的最基本方式,只需创建一个类并继承Thread类,重写run()方法即可。 ``
    的头像 发表于 03-14 16:55 688次阅读