Java基础 -04 List之CopyOnWriteArrayList

java集合有蛮多的类型,今天我们以CopyOnWriteArrayList和Vector进行相关介绍。

CopyOnWriteArrayList

CopyOnWriteArrayList是Java集合框架中的一个线程安全的List实现类。它通过在修改操作时创建一个新的副本来实现线程安全性,因此称为"写时复制"。

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。CopyOnWrite容器即写时复制的容器。通俗的理解是当往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWrite并发容器用于读多写少的并发场景。

复制用法导致内存占用可能过高。

保证数据最终一致性,无法保证实时一致性。


特点

  • 线程安全:CopyOnWriteArrayList是线程安全的,多个线程可以同时读取列表中的元素,而不需要额外的同步措施。这使得它非常适合在多线程环境下进行读取操作。

  • 写时复制:当有线程对CopyOnWriteArrayList进行修改操作(如添加、删除元素)时,它会创建一个新的副本,并在副本上进行修改操作。这样,其他线程仍然可以在原始列表上进行读取操作,不会受到修改操作的影响。一旦修改完成,新的副本会替换原始列表,以确保修改的一致性。

  • 高效的读取操作:由于读取操作不需要进行同步,CopyOnWriteArrayList在读取操作上具有很高的性能。这使得它非常适合在读多写少的场景中使用。

  • 低效的写入操作:由于每次写入操作都需要创建一个新的副本,CopyOnWriteArrayList在写入操作上的性能相对较低。因此,如果应用程序中有大量的写入操作,可能会影响性能。

  • 迭代器的弱一致性:CopyOnWriteArrayList的迭代器提供了弱一致性的保证。即,迭代器在创建时会获取一个快照,并在迭代过程中遍历该快照。这意味着迭代器不会反映出在迭代过程中对列表所做的修改。

CopyOnWriteArrayList适用于读多写少的场景,特别是在需要保证线程安全性的情况下。它常用于事件监听器列表、缓存等场景,其中读取操作远远超过写入操作。

java 复制代码
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 添加元素
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 遍历元素
        for (String element : list) {
            System.out.println(element);
        }

        // 修改元素
        list.set(1, "Grape");

        // 删除元素
        list.remove("Apple");

        // 遍历元素
        for (String element : list) {
            System.out.println(element);
        }
    }

CopyOnWriteArrayList是如何保证安全的

CopyOnWriteArrayList通过"写时复制"(Copy-On-Write)机制来保证线程安全性。具体来说,当有线程对CopyOnWriteArrayList进行修改操作时,它会创建一个新的副本,并在副本上进行修改操作 。这样,其他线程仍然可以在原始列表上进行读取操作,不会受到修改操作的影响。一旦修改完成,新的副本会替换原始列表,以确保修改的一致性。

这种机制的实现步骤如下:

  • 当有线程要对CopyOnWriteArrayList进行修改操作时,它首先会创建一个当前列表的副本。

  • 在副本上进行修改操作,例如添加、删除元素等。

  • 修改完成后,将新的副本替换原始列表,使得其他线程可以看到最新的修改。

通过这种方式,CopyOnWriteArrayList实现了线程安全性,因为每个线程都在自己的副本上进行修改操作,不会影响其他线程的读取操作。这样就避免了传统的同步机制(如锁)带来的竞争和阻塞。

需要注意的是,由于每次修改操作都会创建一个新的副本,CopyOnWriteArrayList在写入操作上的性能相对较低。因此,它更适合于读多写少的场景,特别是在需要保证线程安全性的情况下。

代码层次分析

我们可以从代码层面进行相关分析:

  • 写操作的实现:CopyOnWriteArrayList的写操作(如添加、删除元素)是通过创建一个新的副本来实现的。在添加元素时,会创建一个新的数组,并将原始数组中的元素复制到新数组中,然后在新数组中添加新元素。删除元素时,也是创建一个新的数组,并将原始数组中的元素复制到新数组中,但是不包括要删除的元素。最后,将新数组替换原始数组。

  • 读操作的实现:CopyOnWriteArrayList的读操作是在原始数组上进行的,不需要额外的同步措施。这是因为在写操作期间,读操作仍然可以访问原始数组,不会受到写操作的影响。这样可以保证读操作的线程安全性。

  • 使用volatile关键字:CopyOnWriteArrayList内部使用了volatile关键字来保证多线程之间的可见性。当一个线程修改了列表时,它会将新的副本赋值给volatile修饰的数组引用,以便其他线程可以看到最新的修改。

  • 迭代器的一致性:CopyOnWriteArrayList的迭代器提供了弱一致性的保证。即,迭代器在创建时会获取一个快照,并在迭代过程中遍历该快照。这意味着迭代器不会反映出在迭代过程中对列表所做的修改。

CopyOnWriteArrayList实现了线程安全性。每个线程在修改操作时都会在自己的副本上进行操作,不会影响其他线程的读取操作。这样就避免了传统的同步机制(如锁)带来的竞争和阻塞,提供了高效的线程安全的List实现。

案例说明

假设我们有一个任务列表,多个线程同时对任务列表进行读取和修改操作。我们使用CopyOnWriteArrayList来存储任务列表,并保证线程安全。

java 复制代码
public class TaskList {
    private CopyOnWriteArrayList<String> tasks = new CopyOnWriteArrayList<>();

    public void addTask(String task) {
        tasks.add(task);
    }

    public void removeTask(String task) {
        tasks.remove(task);
    }

    public void printTasks() {
        for (String task : tasks) {
            System.out.println(task);
        }
    }
}

我们创建多个线程来对任务列表进行读取和修改操作:

java 复制代码
public class Main {
    public static void main(String[] args) {
        TaskList taskList = new TaskList();

        // 创建多个线程进行读取和修改操作
        Thread readerThread1 = new Thread(() -> {
            taskList.printTasks();
        });

        Thread readerThread2 = new Thread(() -> {
            taskList.printTasks();
        });

        Thread writerThread1 = new Thread(() -> {
            taskList.addTask("Task 1");
        });

        Thread writerThread2 = new Thread(() -> {
            taskList.removeTask("Task 1");
        });

        // 启动线程
        readerThread1.start();
        readerThread2.start();
        writerThread1.start();
        writerThread2.start();
    }
}

我们创建了两个读取线程(readerThread1和readerThread2)和两个修改线程(writerThread1和writerThread2)。读取线程通过调用printTasks方法来打印任务列表,修改线程通过调用addTask和removeTask方法来添加和删除任务。

由于CopyOnWriteArrayList的线程安全性,多个线程可以同时读取和修改任务列表,而不需要额外的同步措施。读取线程可以在修改线程进行操作的同时访问任务列表,并且不会受到修改操作的影响。

我们可以看到CopyOnWriteArrayList的线程安全性。每个线程在修改操作时都会在自己的副本上进行操作,不会影响其他线程的读取操作。这样就实现了多线程环境下的安全访问和修改任务列表。

相关推荐
晒足以百八十4 分钟前
基于Python 和 pyecharts 制作招聘数据可视化分析大屏
开发语言·python·信息可视化
像污秽一样13 分钟前
Spring MVC初探
java·spring·mvc
计算机-秋大田13 分钟前
基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
LuckyLay16 分钟前
Spring学习笔记_36——@RequestMapping
java·spring boot·笔记·spring·mapping
敲代码不忘补水24 分钟前
生成式GPT商品推荐:精准满足用户需求
开发语言·python·gpt·产品运营·产品经理
清风fu杨柳1 小时前
centos7 arm版本编译qt5.6.3详细说明
开发语言·arm开发·qt
醉颜凉1 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
_小柏_1 小时前
C/C++基础知识复习(20)
开发语言
阿维的博客日记1 小时前
java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程
java·jvm
qiyi.sky1 小时前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat