面试官:你对 CopyOnWriteArrayList 了解吗?
小米(满脸自信):当然,它可是并发编程里的“温柔派”!
面试官:哦?怎么个温柔法?
小米(推了推眼镜):我给你讲个故事你就明白了……
故事开篇:一个神奇的会议室在一个互联网大厂的高楼里,有一间特别的会议室——写时复制会议室(CopyOnWrite Meeting Room)。
这间会议室很有意思,每次会议开始前,房间管理员都会把之前的会议记录“复制”一份,只有会议主持人(写操作)才能在新记录上修改,而参会者(读操作)依旧查看旧版本的记录,互不干扰。等主持人修改完毕,大家才会看到最新的会议纪要。
这种方式确保了会议的稳定性,避免了混乱,但同时也有一些缺点,比如复制过程会消耗一定的时间和内存。
这,就是 CopyOnWriteArrayList 背后的理念!
CopyOnWriteArrayList 是什么?CopyOnWriteArrayList 是 Java 并发工具包(java.util.concurrent)提供的一个线程安全的 ArrayList,它的核心机制是写时复制(Copy-On-Write,简称 COW)。
简单来说:
读操作(get()):不加锁,直接读取旧数据,速度极快。
写操作(add()、set()、remove()):会创建当前数组的副本,在副本上进行修改,修改完成后再更新引用。
它的底层实现可以概括为:
读取操作 直接访问数组,不需要加锁。
写入操作 复制一个新数组,在新数组上进行修改,然后用 volatile 变量更新数组引用,确保可见性。
CopyOnWriteArrayList 的核心代码
可以看到,每次 add() 操作都会复制一个新数组,而不是直接修改原数组。
应用场景:什么时候适合使用 CopyOnWriteArrayList?CopyOnWriteArrayList 非常适合读多写少的场景,例如:
1. 订阅-通知模式
在观察者模式(Observer Pattern)中,通常有多个订阅者(读操作)在监听一个事件(写操作)。CopyOnWriteArrayList 能够保证在通知所有订阅者时,不会因为订阅列表的变更而发生并发问题。
示例:
因为 CopyOnWriteArrayList 在迭代过程中不会受到修改影响,所以可以安全地在多线程环境下进行事件通知。
2. 黑名单、白名单
在一些安全场景中,例如:
黑名单(访问控制列表):需要高频率检查 IP 是否在黑名单里,但黑名单更新较少。
商品推荐白名单:用户查询商品推荐列表的频率远高于修改白名单的频率。
示例:
读操作不会被锁住,查询速度极快,而写操作虽然成本较高,但由于修改次数较少,整体效率依然很高。
3. 系统配置的动态更新
在某些业务系统中,配置项可能需要动态更新,但读取这些配置的频率远高于修改的频率。例如:
AB 测试参数
限流规则
灰度发布的用户名单
使用 CopyOnWriteArrayList 可以确保更新配置时不会影响业务逻辑的稳定性。
示例:
优点
1、读操作无锁,性能极高
由于 get() 操作直接读取 volatile 变量,查询速度接近普通 ArrayList,没有锁竞争。
2、迭代器不会抛出 ConcurrentModificationException
由于 CopyOnWriteArrayList 在修改时会创建新数组,而 iterator() 返回的迭代器是基于旧数组的,因此不会出现 ConcurrentModificationException。
3、适用于读多写少的场景
例如订阅-通知模式、黑名单、缓存、配置等应用场景。
缺点
1、写操作成本高
每次写操作都会创建一个新数组,数据量大时会带来较大的内存消耗和 GC 压力。
2、无法保证实时一致性
由于读取的是旧数组,修改后不会立刻对所有线程可见,而是等到下一次获取时才会看到新数据。
3、适用场景受限
由于写时复制的特性,它不适合写操作频繁的场景,例如高并发的队列操作、计数器等。
总结:面试官的最终评价面试官(点头微笑): 你这个“温柔派”类比很有意思,的确,CopyOnWriteArrayList 适用于读多写少的场景,虽然写操作比较昂贵,但它的线程安全性和无锁读的优势,在某些场景下是无可替代的。
小米(得意地笑): 多谢夸奖!不过如果是写多读少的情况,那我更推荐 ConcurrentHashMap 或 BlockingQueue 之类的方案啦!
面试官(满意地伸出手): 很棒,欢迎加入我们团队!
总结CopyOnWriteArrayList 就像一个会议记录员,每次修改都复制一份新纪录,让读者查看旧版本,写入者改动新版本,最终统一替换。它的优雅之处,在于通过空间换时间,换取了并发环境下的高效读操作。
所以,下次面试官问你:
“CopyOnWriteArrayList适合什么场景?”
你就大声说:适合读多写少!
END我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!