【JAVA】CopyOnWriterArrayList 详解

一、CopyOnWriteArrayLists 是什么

CopyOnWriteArrayList 是 Java 中并发编程中的一个工具类,它是 ArrayList 的线程安全版本。

1、 与普通的 ArrayList 不同,

CopyOnWriteArrayList 在进行修改操作时并不直接操作原始数据,而是先复制一份原始数据,在副本上进行修改,最后再用修改后的副本替换原始数据。这样,读操作可以直接访问原始数据而不需要加锁,提高了读操作的性能,但写操作的性能相对较低。

2、原理

在这里插入图片描述

CopyOnWriteArrayList 的原理是在写操作时,创建一个新的数组,将新数据写入新数组,然后用新数组替换原始数组。这样可以确保在读操作中不会受到写操作的影响,实现了读写分离。

具体步骤如下:

  • 读操作: 读操作直接在当前的数组上进行,不需要加锁,因为读操作不会对数据进行修改,多个线程可以并发进行读操作。
    在这里插入图片描述

  • 写操作:
    写操作在修改数据之前,先复制一份当前的数组,然后在新数组上进行修改。写操作完成后,用新数组替换原始数组。这样,写操作不会影响到正在进行的读操作。
    在这里插入图片描述

  • 删除
    remove 方法也采用了类似的方式使用 ReentrantLock 来进行加锁,确保在对数组进行修改的过程中不会被其他线程干扰。

    • 这个 remove 方法用于删除指定索引位置上的元素。在获取锁之后,它首先获取当前数组,然后根据给定的索引位置获取要删除的元素,并通过数组拷贝的方式生成新的数组,将目标元素排除在外。最后通过 setArray 方法将新的数组设置回去。

    • 同样,使用 finally 块确保在任何情况下都能够释放锁。

在这里插入图片描述

这种通过加锁来保护写操作的方式是典型的写时复制(Copy-On-Write)策略,它使得读操作可以在不加锁的情况下进行,提高了读操作的性能,但在写操作时需要进行数组的复制,因此写操作的开销较大。这种适用于读多写少的场景。

由于写操作是在新数组上进行的,所以写操作不会阻塞正在进行的读操作。这使得 CopyOnWriteArrayList 在读多写少的场景中表现良好。

需要注意的是,由于写操作是在新数组上进行,所以写操作的代价相对较高,尤其是在数组较大的情况下。因此,CopyOnWriteArrayList 适用于读多写少的场景,而不适合写操作频繁的高并发场景。

3、设计思路:

  • 写时复制: 主要特点是写操作时创建一个新的数组,将修改后的数据写入新数组,然后用新数组替换原数组。这样做的好处是在写操作时不会影响正在进行的读操作,实现了读写分离。
    在这里插入图片描述
  • 加锁保证原子性: 在写操作时使用 synchronized 关键字对关键代码块进行加锁,确保在写操作过程中其他线程不会修改数组。这样可以保证写操作的原子性。上图写操作的源码中使用了
    ReentrantLock 来进行加锁。这是一种替代 synchronized
    关键字的方式,它提供了更灵活的锁定和解锁机制,并支持可重入性。
  • 数组拷贝: 写操作在修改数据之前,会先将当前数组进行拷贝,然后在新数组上进行修改。这样可以避免写操作影响正在进行的读操作。
  • 适用于读多写少场景: 由于写操作涉及数组拷贝,写操作的代价相对较高,适用于读多写少的场景。

4、可以应用到其他设计:

  • 缓存更新: 在需要频繁读取但很少更新的缓存场景中,可以使用类似的读写分离的设计思路,通过写时复制的方式保证在更新缓存时不影响正在进行的读取操作。
  • 配置管理: 在配置管理中,如果有多个线程读取配置信息而很少修改配置信息的情况下,可以使用类似的读写分离机制,确保在修改配置信息时不影响正在读取配置信息的线程。

二、为什么要用 CopyOnWriteArrayLists

线程安全CopyOnWriteArrayList 提供了一种线程安全的读写操作机制。在读操作中,不需要加锁,而写操作则通过复制原始数据,避免了对原始数据的并发修改。

避免读写冲突: 在传统的 ArrayList 中,如果一个线程在读取的同时,另一个线程正在修改列表,可能导致读写冲突,需要额外的同步机制。而 CopyOnWriteArrayList 通过写时复制的方式,确保在写操作时不会影响正在进行的读操作,避免了读写冲突。

适用于读多写少的场景: 由于写操作需要复制整个数组,因此写操作的性能较差。但在读多写少的场景中,由于读操作不需要加锁,可以提高读操作的性能。

支持弱一致性迭代器CopyOnWriteArrayList 的迭代器可以提供弱一致性,即在迭代的过程中如果有其他线程对列表进行修改,迭代器仍然可以继续遍历旧的数据。这在某些场景下是合理的。

应用于特定场景: 适用于一些特定的场景,例如配置管理、事件监听器列表等,其中读操作较为频繁,写操作较为稀少。或者设计框架的时候,用来配置拦截器等数据。

需要注意的是,CopyOnWriteArrayList 的适用性并非一刀切,它在某些场景下有优势,但在某些高并发写操作频繁的场景下,可能不是最佳选择。开发人员需要根据具体场景和需求进行选择。

三、如何应用 CopyOnWriteArrayLists:

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 写操作
        list.add("Item1");
        list.add("Item2");
        list.add("Item3");

        // 读操作
        for (String item : list) {
            System.out.println(item);
        }
    }
}

在上面的例子中,CopyOnWriteArrayList 被用于存储字符串,并且在迭代时不需要额外的同步措施。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值