如何基于AtomicReference实现ArrayList的原子contains操作(非全锁/非线程安全集合)
基于AtomicReference实现ArrayList的原子读写操作
核心思路
由于ArrayList本身并非线程安全,直接在共享实例上执行add/contains会引发并发问题(比如遍历期间结构修改抛出ConcurrentModificationException,或出现数据不一致)。借助AtomicReference的原子性引用更新特性,我们可以通过**写时复制(Copy-On-Write)**模式实现线程安全,同时避免全局锁。
原子性contains()操作的实现
要原子性执行contains(),核心是获取一个固定的ArrayList快照,再基于这个快照完成查询——一旦拿到快照引用,后续AtomicReference中的列表被修改也不会影响当前查询,保证了查询操作基于某个时刻的列表状态完整执行,天然具备原子性。
具体实现非常直接:
- 通过AtomicReference的
get()方法原子性获取当前列表的引用 - 在该引用指向的列表实例上执行
contains(givenElement)
这里完全不需要compareAndSet(),因为读操作不需要修改引用,只需要原子性拿到当前有效的列表实例即可。只要写操作遵循写时复制逻辑,读操作拿到的永远是一个结构稳定的快照(写操作不会修改原列表,只会替换整个引用)。
原子性add()操作的实现
为了配合读操作的原子性,写操作必须遵循写时复制逻辑:
- 获取当前AtomicReference中的列表引用
- 创建该列表的副本(如
new ArrayList<>(currentList)) - 向副本中添加新元素
- 通过
compareAndSet(currentList, copyList)原子性替换原引用;如果期间有其他线程修改了原引用,则重复上述步骤循环重试
代码示例
import java.util.ArrayList; import java.util.concurrent.atomic.AtomicReference; public class AtomicArrayListDemo { private final AtomicReference<ArrayList<String>> listRef = new AtomicReference<>(new ArrayList<>()); // 原子性添加元素 public void add(String element) { while (true) { ArrayList<String> current = listRef.get(); ArrayList<String> newList = new ArrayList<>(current); newList.add(element); // CAS替换成功则退出循环 if (listRef.compareAndSet(current, newList)) { break; } // 失败说明有其他线程修改了引用,重试 } } // 原子性执行contains查询 public boolean contains(String element) { // 原子性获取快照后执行查询 ArrayList<String> current = listRef.get(); return current.contains(element); } }
关键说明
- 为什么
contains不需要CAS?:get()是原子操作,拿到的current引用对应某个时刻的列表实例,这个实例不会被任何线程修改(所有写操作都基于副本完成后替换引用),因此在current上执行contains是安全的,不会出现并发修改问题。 - 适用场景:这种模式适合读多写少的场景,因为写操作需要复制整个列表,写频繁时开销会较高。
- 一致性保证:提供最终一致性而非强一致性——线程执行
add后,其他线程需等待CAS替换完成才能看到新元素,但每个读操作看到的都是一个状态一致的列表快照。
内容的提问来源于stack exchange,提问作者Rahul Raj
相关产品推荐
相关产品推荐

