为什么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 等。

相关推荐
hello 早上好3 小时前
Spring Boot 核心启动机制与配置原理剖析
java·spring boot·后端
武子康3 小时前
大数据-112 Flink DataStream API :数据源、转换与输出 文件、Socket 到 Kafka 的完整流程
大数据·后端·flink
Terio_my5 小时前
Spring Boot 缓存技术
spring boot·后端·缓存
IT_陈寒5 小时前
Python 3.12 性能暴增50%!这5个新特性让老项目直接起飞
前端·人工智能·后端
你的人类朋友5 小时前
【操作系统】说说 x86 和 x64
后端·程序员·操作系统
半夏知半秋5 小时前
基于skynet框架业务中的gateway实现分析
服务器·开发语言·后端·学习·gateway
青柠编程14 小时前
基于Spring Boot的选课管理系统架构设计
java·spring boot·后端
码事漫谈17 小时前
C++内存泄漏排查:从基础到高级的完整工具指南
后端
王嘉俊92517 小时前
ThinkPHP 入门:快速构建 PHP Web 应用的强大框架
开发语言·前端·后端·php·框架·thinkphp