集合-04

举报
kwan的解忧杂货铺 发表于 2024/05/27 22:15:27 2024/05/27
【摘要】 四.CopyOnWriteArrayList 1.什么是 CopyOnWriteArrayListCopyOnWriteArrayList 是 Java 中的一个线程安全的 List 实现类,它是 ArrayList 的线程安全版本。CopyOnWriteArrayList 允许多个线程同时读取 List 中的元素,而不需要进行额外的同步操作。当有写操作时,CopyOnWriteArray...

四.CopyOnWriteArrayList

1.什么是 CopyOnWriteArrayList

CopyOnWriteArrayList 是 Java 中的一个线程安全的 List 实现类,它是 ArrayList 的线程安全版本。CopyOnWriteArrayList 允许多个线程同时读取 List 中的元素,而不需要进行额外的同步操作。当有写操作时,CopyOnWriteArrayList 会将 List 中的元素复制一份,然后进行修改,修改完成后再将新的 List 替换原来的 List。

2.CopyOnWriteArrayList 特点

CopyOnWriteArrayList 的主要特点如下:

  1. 线程安全:CopyOnWriteArrayList 是线程安全的,可以在多线程环境中安全地使用。
  2. 读操作不锁定:CopyOnWriteArrayList 的读操作不需要进行锁定,因此读操作的性能很高。
  3. 写操作复制数组:CopyOnWriteArrayList 的写操作会将 List 中的元素复制一份,然后进行修改,修改完成后再将新的 List 替换原来的 List。因此,写操作的性能较低,而且会消耗更多的内存。

特点:

  • 写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
  • 写操作需要加锁,防止并发写入时导致写入数据丢失。
  • 写操作结束之后需要把原始数组指向新的复制数组。
  • 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。
  • 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右。
  • 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
  • 写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待,读操作的性能得到大幅度提升。

3.CopyOnWriteArrayList 使用场景

CopyOnWriteArrayList 的使用场景主要是在读操作远远多于写操作的情况下,例如缓存和事件监听器列表等。

CopyOnWriteArrayList 为 ArrayList 的线程安全版本,俗称写时复制,此思想在多种中间件中都有使用,比如 nacos,sentinel 等中间件.空间换时间,保证读的最大效率,牺牲一点写的性能.多使用在都多写少的场景下.由于读操作根本不会修改原有的数据,因此如果每次读取都进行加锁操作,其实是一种资源浪费。我们应该允许多个线程同时访问 List 的内部数据,毕竟读操作是线程安全的。

CopyOnWriteArrayList 其底层数据结构也是数组,但是在写操作的时候都会拷贝一份数据进行修改,修改完后替换掉老数据,从而保证只阻塞写操作,读操作不会阻塞,实现读写分离。

4.CopyOnWriteArrayList 简介

  • CopyOnWriteArrayList 线程安全,默认容量为长度为 1 的 Object 数组,允许元素为 null。
  • 使用 ReentrantLock 可重入锁,保证写操作的线程安全。
  • 在写操作时,都需要拷贝一份数组,然后在拷贝的数组中进行相应的操作,最后再替换旧数组。
  • 采用读写分离的实现,写操作加锁,读操作不加锁,而且写操作会占用较多空间,因此适用于读多写少的场景。
  • CopyOnWriteArrayList 能保证最终一致性,但是不保证实时一致性,因为在写操作未完,而进行读操作时,由于写操作在新数组中操作,并不会影响到读操作,这是造成数据不一致性。
  • CopyOnWriteArrayList 返回迭代器不会抛出 ConcurrentModificationException 异常,即它不是 fail-fast 机制的

5.CopyOnWriteArrayList 原理

  • CopyOnWriteArrayList 默认容量是数组长度为 1 的 Object 类型数组。

  • 操作 array 底层数组,都是通过 setArray 和 getArray 来进行的。

/** The lock protecting all mutators */
// 使用可重入锁进行加锁,保证线程安全
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
// 底层数据结构,注意这里用volatile修饰,确定了多线程情况下的可见性
private transient volatile Object[] array;

CopyOnWriteArrayList原理:

  1. CopyOnWriteArrayList 实现了 List 接口,因此它是一个队列。
  2. CopyOnWriteArrayList 包含了成员 lock。每一个 CopyOnWriteArrayList 都和一个监视器锁 lock 绑定,通过 lock,实现了对 CopyOnWriteArrayList 的互斥访问。
  3. CopyOnWriteArrayList 包含了成员 array 数组,这说明 CopyOnWriteArrayList 本质上通过数组实现的。
  4. CopyOnWriteArrayList 的“动态数组”机制 – 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”。这就是它叫做 CopyOnWriteArrayList 的原因!CopyOnWriteArrayList 就是通过这种方式实现的动态数组;不过正由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很 低;但是单单只是进行遍历查找的话,效率比较高。
  5. CopyOnWriteArrayList 的“线程安全”机制 – 是通过 volatile 和 ReentrantLock 来实现的。
  6. CopyOnWriteArrayList 是通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的 保证。
  7. CopyOnWriteArrayList 通过监视器锁 ReentrantLock 来保护数据。在“添加/修改/删除”数据时,会先“获取监视器锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”;这样,就达到了保护数据的目的。

6.CopyOnWriteArrayList 的 add 方法

add 操作是加了锁的,利用了 ReentrantLock 进行加锁,注意使用该方式进行加锁,需要手动释放。

整个过程是新建了一个新的数组(数组长度加 1),然后将新元素放在最后一位,最后替换掉旧数组

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 注意这里将数组长度加1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 新元素放在最后一位
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

7.写时复制的缺点?

内存占用问题:

  • 因为 CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,这一点会占用额外的内存空间。
  • 在元素较多或者复杂的情况下,复制的开销很大
  • 复制过程不仅会占用双倍内存,还需要消耗 CPU 等资源,会降低整体性能。

数据一致性问题:由于 CopyOnWrite 容器的修改是先修改副本,所以这次修改对于其他线程来说,并不是实时能看到的,只有在修改完之后才能体现出来。如果你希望写入的的数据马上能被其他线程看到,CopyOnWrite 容器是不适用的。volatile 关键字,数据可见性

private transient volatile Object[] array;
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。