为什么CopyOnWriteArrayList是线程安全的?

为什么ArrayList 是非线程安全的集合类?

arduino 复制代码
public class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add("1");
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

运行结果会报错,或者打印出来的不对、

原因

ArrayList 是非线程安全的集合类

多个线程同时对 list 进行修改操作(add)和读取操作(toString 内部会遍历)

当一个线程正在遍历时,另一个线程修改了集合结构,就会抛出 ConcurrentModificationException

解决方案

1.使用synchronized关键字

typescript 复制代码
public class Demo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Object lock = new Object();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    list.add("1");
                    System.out.println(list);
                }
            }, String.valueOf(i)).start();
        }
    }
}

2.使用vector

Vector 是 Java 中的一个线程安全的集合类

与 ArrayList 相比,Vector 的方法都是同步的(synchronized)

适用于多线程环境下的使用场景

查看源码可以看到里边就是使用的synchronized关键字。

3.使用Collections.synchronizedList()工具类的方法

typescript 复制代码
public class Demo {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        Object lock = new Object();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    list.add("1");
                    System.out.println(list);
                }
            }, String.valueOf(i)).start();
        }
    }

4.使用 CopyOnWriteArrayList(推荐)

arduino 复制代码
import java.util.concurrent.CopyOnWriteArrayList;

public class Demo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add("1");
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList

CopyOnWriteArrayList这个是专门设计解决并发环境的线程安全集合。

我们去查看源码:

写操作

你看到这里边也使用了synchronized关键字,使用了lock;这是因为写的时候需要保证数据的一致性。

但是读操作就不需要加锁

CopyOnWriteArrayList 是如何工作的 ?

  • 读操作 (如 get()size())是非常高效的,因为它不涉及锁。每次读操作直接访问底层的数组(它的副本),从而保证了线程安全。
  • 写操作 (如 add()remove()set())需要复制整个数组,修改副本,然后用新的数组替换旧的数组。这种策略避免了读操作被修改中的数组影响,但同时,写操作在多线程中必须进行同步,以防止在修改数组时出现不一致。

为什么同时使用 synchronizedlock

CopyOnWriteArrayList 中使用了synchronizedlock

synchronized lock 的区别

  • synchronized 是 Java 内置的同步机制 ,直接在方法或代码块上加锁,锁的粒度较大,通常适用于简单的同步需求。
  • lock 是通过显式的锁 (如 ReentrantLock)来控制访问,它提供了比 synchronized 更强大的功能,如尝试加锁(tryLock()),中断锁等待等。lock 允许更精细的控制,可以在获取锁时设置超时、获取公平性等。

CopyOnWriteArrayList 中的应用

  • CopyOnWriteArrayList 是线程安全的 集合类,它的底层实现通常会在添加、删除、更新元素时创建一个新的数组,并通过锁来确保写操作的线程安全。
  • locksynchronized 可能用于不同场景中的加锁:
  • 使用 lock
    • CopyOnWriteArrayList 的内部,lock 可能用于更精细的控制,特别是在 写操作 时对底层数组进行复制和修改的过程中。lock 提供了 可中断的锁尝试获取锁公平性控制 等高级功能,这些都可能被用在性能要求较高的场景中。
  • 使用 synchronized
    • synchronized 可能用于 其他部分的同步 ,例如在某些写操作(如 add())的开始和结束之间。synchronized 更适合对临界区(即操作共享资源的代码块)进行加锁。
ini 复制代码
public boolean add(E e) {
    synchronized (lock) {  // 使用 lock 来同步
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}

在这段源码中,synchronized (lock) 用于 同步访问,保证多个线程在执行添加元素的操作时不会发生数据冲突。

总结

优点 :线程安全、高效读操作、不需要显式加锁、适用于读多写少 的场景。
缺点 :写操作性能差、内存开销大、不适合频繁写操作 的场景。

适用场景:适用于读多写少、写操作较少、并发读操作频繁的场景。
CopyOnWriteArrayList 在某些高并发环境中是非常有用的,尤其是希望避免锁竞争、提高读取性能 时。然而,它并不适合所有的场景,特别是写操作较多的情况下,可能需要考虑其他更适合的并发数据结构,如 ConcurrentLinkedQueue ReentrantLock 等。

相关推荐
蚂蚁背大象17 分钟前
Rust 所有权系统是为了解决什么问题
后端·rust
子玖2 小时前
go实现通过ip解析城市
后端·go
Java不加班2 小时前
Java 后端定时任务实现方案与工程化指南
后端
心在飞扬2 小时前
RAG 进阶检索学习笔记
后端
Moment2 小时前
想要长期陪伴你的助理?先从部署一个 OpenClaw 开始 😍😍😍
前端·后端·github
Das1_2 小时前
【Golang 数据结构】Slice 底层机制
后端·go
得物技术2 小时前
深入剖析Spark UI界面:参数与界面详解|得物技术
大数据·后端·spark
古时的风筝2 小时前
花10 分钟时间,把终端改造成“生产力武器”:Ghostty + Yazi + Lazygit 配置全流程
前端·后端·程序员
Cache技术分享2 小时前
340. Java Stream API - 理解并行流的额外开销
前端·后端