Concurrent 并发集合

Concurrent 并发集合

1、概述

原先使用的 ArrayList、HashMap 、HashSet、等集合都是线程不安全的,因此 java.util.concurrent 包提供了对应的并发集合类

接口非线程安全线程安全
ListArrayListCopyOnWriteArrayList
MapHashMapConcurrentHashMap
SetHashSet TreeSetCopyOnWriteArraySet
QueueArrayDeque LinkedListArrayBlockingQueue LinkedBlockingQueue
DequeArrayDeque LinkedListLinkedBlockingDeque

这些并发集合与非线程安全的集合类完全相同

2、CopyOnWriteArrayList

(1)基本思想

当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器

(2)数据结构

数组

CopyOnWriteArrayList相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用ReentrantLock实现,允许多个线程并发读取,但只能有一个线程写入。

(3)基本方法
1、add()

添加新元素至集合时,会将当前数组复制一个新数组,并将新元素添加至新数组,最后替换原数组】

执行过程中使用 ReentrantLock 加锁,保证线程安全

源码展示:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;

        // 复制出新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);

        // 把新元素添加到新数组里
        newElements[len] = e;

        // 把原数组引用指向新数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
2、get()

根据指定下标,到原数组中读取元素。读取过程中不加锁,允许多个线程并发读取。但是如果读取的时候,有其它线程向集合中添加新元素,此时仍然读取到的是旧数据因为添加操作没有对原数组加锁。

源码展示

public E get(int index) {
    // 根据指定下标,从原数组中读取元素
    return get(getArray(), index);
}
private E get(Object[] a, int index) {
    return (E) a[index];
}
3、remove()

删除指定下标元素。根据指定下标,从原数组中,Copy复制其它元素至新数组,最后替换原数组。

源码展示

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        
        if (numMoved == 0)
            // 复制原数组中,除最后一个元素以外的所有元素,至新数组
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 复制原数组中,除删除元素以外的所有元素,至新数组
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
(4)CopyOnWriteArrayList 的特性
  • 在保证并发读取的前提下,确保了写入时的线程安全;
  • 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;
  • 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
  • CopyOnWriteArrayList 由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性

3、ConcurrentHashMap

(1)概述

ConcurrentHashMap是一个支持高并发更新与查询的哈希表(类似HashMap)。在保证安全的前提下,查询不需要锁定。与Hashtable不同,该类不依赖于synchronization去保证线程操作的安全。

(2)数据结构
1、JDK 1.7

分段数组+链表,采用分段锁(Segment)对数组进行分割分段每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率;

过程:

  • 首先将数据分为一段一段的存储
  • 然后给每一段数据配一把锁
  • 当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问
  • 一个 ConcurrentHashMap 里包含一个 Segment 数组
  • Segment 的结构和 HashMap 类似,是一种数组和链表结构
  • 一个 Segment 包含一个 HashEntry 数组
  • 每个 Entry 是一个链表结构的元素
  • 每个 Segment 守护着一个 Entry 数组里的元素
  • 当对 HashEntry 数[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzEZ2Few-1689591900532)(E:\APESOURCE\学习笔记\image\JDK1.7 ConcurrentHashMap 结构.png)]组的数据进行修改时,必须首先获得对应的 Segment 的锁。
2、JDK1.8

数组+链表 + 红黑树,使用 synchronized 和 CAS 来进行并发控制;

synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率提升 N 倍;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qIqohnnl-1689591900533)(E:\APESOURCE\学习笔记\image\Java 8 ConcurrentHashMap 结构.png)]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kⅈꫛᧁ269

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值