Skip to Content
面试题库Juc[面试题] CopyOnWriteArrayList 的底层原理是什么?为什么它适合读多写少?

[面试题] CopyOnWriteArrayList 的底层原理是什么?为什么它适合读多写少?

📖 版块 A:知识讲解版 (温故知新)

  • 定义CopyOnWriteArrayList 是 JUC 提供的线程安全 List,实现思想是 写时复制

  • 核心原理

    1. 读操作直接读取内部数组,不加锁。
    2. 写操作时先加锁。
    3. 复制出一份新数组,在新数组上完成修改。
    4. 最后把内部引用指向新数组。
  • 优点

    1. 读操作几乎无锁,性能很好。
    2. 读线程不会被写线程阻塞。
    3. 迭代时基于快照,不会抛 ConcurrentModificationException
  • 缺点

    1. 写操作开销大,需要复制整个数组。
    2. 占用额外内存。
    3. 读到的是快照,可能不是最新实时数据。
  • 适用场景

    1. 配置项列表。
    2. 监听器列表。
    3. 黑白名单缓存。
    4. 读远远多于写的共享集合。

🎤 版块 B:面试实战版 (高手逐字稿)

Q:CopyOnWriteArrayList 的底层原理是什么?为什么它适合读多写少?

💬 高手逐字稿

CopyOnWriteArrayList 的核心思想其实都写在名字里了,就是“写的时候复制一份”。它并没有像普通线程安全容器那样,把所有读写操作都围在一把大锁里,而是做了一个很激进的取舍:读尽量不加锁,写的时候我来承担全部代价

具体做法是这样的。内部真实数据是一个数组。读操作直接读取当前数组引用,不需要加锁;而写操作,比如 addsetremove,会先拿锁,然后复制出一份新数组,在新数组上完成修改,最后再把内部数组引用一次性切换过去。这样读线程始终读的是某个稳定数组,不会读到一半结构变化。

它为什么适合读多写少?因为它把成本几乎都压到了写操作上。一次写可能要复制整份数组,元素越多,成本越高;但读操作非常轻量,多个线程并发读时基本没有锁竞争。所以如果你的场景是“偶尔更新,频繁读取”,比如监听器列表、配置白名单、事件订阅者集合,这种模型就很合适。

它还有一个很典型的特征,就是快照语义。迭代器拿到的是创建那一刻的数组快照,后续即使有线程在修改集合,这个迭代器也不会抛 ConcurrentModificationException。但反过来说,它读到的也可能不是最新数据,所以它并不适合那种对实时一致性要求很高的业务列表。

所以我的理解是:CopyOnWriteArrayList 不是“通用线程安全 List”,而是一个非常典型的“用空间和写成本换读性能”的特化容器。场景对了很好用,场景不对就会很浪费。

最后更新于:
⏱️

登录后开始阅读

当前 MVP 版本只开放邮箱密码登录注册。完成登录后即可进入面试题内容。

还没有账号?先去注册