资讯专栏INFORMATION COLUMN

Java 中的线程安全容器

Seay / 3027人阅读

摘要:一同步容器常用的一些容器例如都不是线程安全的,最简单的将这些容器变为线程安全的方式,是给这些容器所有的方法都加上关键字。为了降低哈希冲突的成本,在链表长度超过时,将链表转换为红黑树。

一、同步容器

常用的一些容器例如 ArrayList、HashMap、都不是线程安全的,最简单的将这些容器变为线程安全的方式,是给这些容器所有的方法都加上 synchronized 关键字。

Java 的 Collections 中实现了这些同步容器:

简单的使用如下:

List list = Collections.synchronizedList(new ArrayList<>());

Map map = Collections.synchronizedMap(new HashMap<>());

Set set = Collections.synchronizedSet(new HashSet<>());

同步容器虽然简单,但是相应的效率较低,因为锁的粒度较大。

循环遍历同步容器

如果在遍历同步容器的时候,组合了多个方法,这会可能会存在竞态条件,仍然不是线程安全的。解决的办法便是对容器加锁。例如下面这样:

public static void main(String[] args) {
    List list = Collections.synchronizedList(new ArrayList<>());
    
    //省略添加数据的操作
    
    String[] str = new String[list.size()];
    int k = 0;
    synchronized (list){
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            str[k ++] = iterator.next();
        }
    }
}
二、并发容器

Java 中还提供了一系列并发容器,相比于同步容器,其性能更好。并发容器共分为了四类:List、Map、Set、Queue。

1. List

List 中一个最主要的实现类是 CopyOnWriteArrayList ,CopyOnWrite,即写时复制,这样的好处是读操作是无锁的。

其实现原理是内部维护了一个数组,内部变量 array 指向了这个数组。需要写时,并不是在原数组上操作,而是将数组复制一份,在拷贝的数组中进行写。完成后,将 array 指向新的数组。这样一来,读写之间不互斥,效率得到了很大的提升。

需要注意的是 CopyOnWriteArrayList 适用于读多写少的场景,并且需要接受读写的暂时不一致,因为在写的时候,并行的读操作可能并不能马上看到写的结果。

2. Map

Map 的两个主要实现类是 ConcurrentHashMapConcurrentSkipListMap,两者主要的区别是:前者是无序的,后者是有序的。

2.1 ConcurrentHashMap

在 Java 1.7 中,ConcurrentHashMap 的实现使用的是分段锁技术,其内部主要的数据结构是 Segment 和 HashEntry,ConcurrentHashMap 包含了一个 Segment 数组,每个 Segment 又包含一个 HashEntry 数组,每个 HashEntry 是一个存储数据的链表结构。

其中 Segment 继承了 ReentrantLock,每个 Segment 都有对应的锁,需要修改数据的时候,需要获取这把锁。修改不同的 Segment 数据,则完全可以并行,效率得到了提升。示意图如下:

Java 1.8 又对 ConcurrentHashMap 做了较大的改进,放弃了分段锁的技术。结构和 Java 1.8 中的 HashMap 类似,采用的是数组+链表/红黑树来实现。为了降低哈希冲突的成本,在链表长度超过 8 时,将链表转换为红黑树。使用 CAS 和 synchronized 解决并发问题,锁住链表或者红黑树的头节点,只要没有哈希冲突,则不会出现并发问题。示意图如下:

2.2 ConcurrentSkipListMap

ConcurrentSkipListMap 保证有序的主要原因是,底层使用的是跳表这种数据结构,关于跳表的介绍,你可以查看数据结构中的内容。

3. Set

Set 的两个实现是 CopyOnWriteArraySetConcurrentSkipListSet

和前面说到的 CopyOnWriteArrayList 、ConcurrentSkipListMap 实现的原理类似。

4. Queue

队列可以从两方面进行分类:

单端和双端:单端队列指的是只能在队首出队,队尾出队,而双端队列指的是队首和队尾均可入队和出队。

阻塞和非阻塞:阻塞队列指的是,当队列满的时候,入队列阻塞;当队列空的时候,出队列阻塞。

Java 中,单端队列使用 queue 标识,双端队列使用 deque 标识。

4.1 单端阻塞队列

常用的实现类有:ArrayBlockingQueueLinkedBlockingQueuePriorityQueue

4.2 单端非阻塞队列

其实现类是 ConcurrentLinkedQueue

4.3 双端阻塞队列

其实现类是 LinkedBlockingDeque

4.4 双端非阻塞队列

其实现类是 ConcurrentLinkedDeque

使用其实都非常地简单,就是入队出队之类的操作,这里不再赘述了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74635.html

相关文章

  • Java并发,volatile+不可变容器对象能保证线程安全么?!

    摘要:同样,用类型的变量来保存这些值也不是线程安全的。仅保证可见性,无法保证线程安全性。并且返回的结果是对象,是局部变量,并未使对象逸出,所以这里也是线程安全的。 《Java并发编程实战》第3章原文 《Java并发编程实战》中3.4.2 示例:使用Volatile类型来发布不可变对象 在前面的UnsafeCachingFactorizer类中,我们尝试用两个AtomicReferences变...

    tyheist 评论0 收藏0
  • [Java并发-11] 并发容器的使用

    摘要:同步容器及其注意事项中的容器主要可以分为四个大类,分别是和,但并不是所有的容器都是线程安全的。并发容器及其注意事项在版本之前所谓的线程安全的容器,主要指的就是同步容器,当然因为所有方法都用来保证互斥,串行度太高了,性能太差了。 Java 并发包有很大一部分内容都是关于并发容器的,因此学习和搞懂这部分的内容很有必要。 Java 1.5 之前提供的同步容器虽然也能保证线程安全,但是性能很差...

    legendaryedu 评论0 收藏0
  • CopyOnWriteArrayList你都不知道,怎么拿offer?

    摘要:今天主要讲解的是本文力求简单讲清每个知识点,希望大家看完能有所收获一和回顾线程安全的和我们知道是用于替代的,是线程安全的容器。使用迭代器遍历时不需要显示加锁,看看与方法的实现可能就有点眉目了。 前言 只有光头才能变强 showImg(https://segmentfault.com/img/remote/1460000016931828?w=1120&h=640); 前一阵子写过一篇C...

    noONE 评论0 收藏0
  • 想进大厂?50个多线程面试题,你会多少?(一)

    摘要:下面是线程相关的热门面试题,你可以用它来好好准备面试。线程安全问题都是由全局变量及静态变量引起的。持有自旋锁的线程在之前应该释放自旋锁以便其它线程可以获得自旋锁。 最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员...

    wow_worktile 评论0 收藏0
  • java线程安全支持有哪些?

    摘要:它能阻塞一组线程直到某个事件发生。与闭锁的区别所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其它线程。闭锁一旦进入终止状态,就不能被重置,它是一次性对象,而栅栏可以重置。 同步容器。它的原理是将状态封装起来,并对每个公有方法都实行同步,使得每次只有1个线程能够访问容器的状态。 Vector和HashTable Collections.synchroni...

    sewerganger 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<