CopyOnWriteArrayList
读多写少的场景,用什么 List?
synchronizedList 性能差,Vector 同样有锁。有没有读操作无锁的方案?
CopyOnWriteArrayList:写时复制,读操作完全无锁。
核心原理
┌──────────────────────────────────────────────────┐
│ CopyOnWriteArrayList 原理 │
├──────────────────────────────────────────────────┤
│ │
│ 写操作: │
│ 1. 复制整个数组 │
│ 2. 在副本上修改 │
│ 3. 用新数组替换旧数组(原子操作) │
│ │
│ ┌─────────────┐ │
│ │ 旧数组 │ ← 读操作继续用这个 │
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ 新数组 │ ← 写操作创建的新副本 │
│ └─────────────┘ │
│ │
│ 读操作:无锁,直接读取 │
│ │
└──────────────────────────────────────────────────┘写操作的本质:
java
// 写操作简化逻辑
public boolean add(E e) {
synchronized (lock) {
Object[] newElements = Arrays.copyOf(elements, elements.length + 1);
newElements[elements.length] = e;
elements = newElements; // 原子替换
}
}基础用法
java
public class CopyOnWriteDemo {
private static final CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();
public static void main(String[] args) {
// 添加
list.add("元素1");
list.add("元素2");
list.addIfAbsent("元素1"); // 不重复添加
System.out.println("列表: " + list);
// 读取(无锁)
for (String elem : list) {
System.out.println("读取: " + elem);
}
// 遍历过程中,其他线程的修改可能看到或看不到
// 但不会抛出 ConcurrentModificationException
// 修改
list.set(0, "新元素");
System.out.println("修改后: " + list);
}
}弱一致性:迭代器的特点
java
public class WeakConsistencyDemo {
private static final CopyOnWriteArrayList<Integer> list =
new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 迭代器持有的是创建时的快照
Iterator<Integer> iterator = list.iterator();
// 迭代过程中修改
list.add(100);
// 迭代器看不到新元素(弱一致性)
System.out.println("迭代器看到的: ");
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
// 但 list 本身有新元素
System.out.println("\nlist 实际: " + list);
}
}输出可能:
迭代器看到的: 0 1 2 3 4 5 6 7 8 9
list 实际: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]迭代器不会 ConcurrentModificationException,因为它操作的是快照。
读多写少场景演示
java
public class ReadHeavyDemo {
private static final CopyOnWriteArrayList<String> cache =
new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
// 填充数据
for (int i = 0; i < 1000; i++) {
cache.add("数据-" + i);
}
CountDownLatch latch = new CountDownLatch(100);
// 100 个读线程
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
// 读操作无锁,高性能
cache.contains("数据-" + j);
}
latch.countDown();
}).start();
}
// 偶尔写入
new Thread(() -> {
for (int i = 0; i < 10; i++) {
cache.add("新数据-" + i);
try {
Thread.sleep(100);
} catch (Exception e) {}
}
}).start();
latch.await();
System.out.println("最终大小: " + cache.size());
}
}CopyOnWriteArraySet
CopyOnWriteArrayList 的 Set 版本:
java
public class CopyOnWriteSetDemo {
private static final CopyOnWriteArraySet<String> set =
new CopyOnWriteArraySet<>();
public static void main(String[] args) {
set.add("A");
set.add("B");
set.add("A"); // 重复不添加
System.out.println("Set: " + set);
System.out.println("大小: " + set.size());
}
}底层还是 CopyOnWriteArrayList,只是自动去重。
与其他 List 对比
| 特性 | CopyOnWriteArrayList | synchronizedList | Vector |
|---|---|---|---|
| 读操作 | 无锁,最高性能 | 有锁 | 有锁 |
| 写操作 | 复制整个数组 | 有锁 | 有锁 |
| 迭代器 | 弱一致,不抛异常 | 快速失败 | 快速失败 |
| 适用场景 | 读多写少 | 写多读少 | 不推荐 |
使用决策
需要线程安全的 List?
│
├── 读多写少(配置、黑名单、白名单)
│ └── CopyOnWriteArrayList ✅
│
├── 写多读少(并发写入)
│ └── Collections.synchronizedList ❌ 性能差
│ └── JUC 的其他并发 List
│
└── 需要遍历时修改
└── Collections.synchronizedList(注意并发修改)常见错误
错误一:写操作频繁
java
// ❌ 错误:CopyOnWriteArrayList 不适合写多
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(i); // 每次都复制数组,极慢!
}
// ✅ 正确:写多时用 synchronizedList
List<Integer> list = Collections.synchronizedList(new ArrayList<>());错误二:批量操作误用
java
// ❌ 错误:批量操作不是原子的
list.add("A");
list.add("B");
list.add("C");
// 迭代器可能在批量操作中间看到不一致状态
// ✅ 正确:需要原子批量操作时,使用 synchronized
synchronized (list) {
list.add("A");
list.add("B");
list.add("C");
}注意事项
- 写操作开销大:复制整个数组,大数据量时内存压力大
- 内存占用高:旧数组需要等待 GC
- 不适合写多场景:频繁写入时性能差
- 迭代器弱一致:可能看不到最新修改
- 适合小型数据集:如配置列表、黑白名单
要点回顾
- CopyOnWriteArrayList 写时复制数组,读操作完全无锁
- 读多写少场景性能优秀
- 迭代器弱一致,不抛 ConcurrentModificationException
- 写操作复制整个数组,开销大,不适合写多场景
- 适合:配置列表、黑白名单、小型只读缓存
相关链接
- JUC 集合选择 - 根据场景选择合适的集合
- ConcurrentHashMap - 高性能并发 Map
- ConcurrentQueue - 非阻塞队列
