Java集合之【CopyOnWrite和Collections.synchronizedList()的区别】
CopyOnWriteArrayList 介绍
什么是 CopyOnWriteArrayList
适合读多写少的场景
是一个线程安全的List实现,特点是写时复制
当CopyOnWriteArrayList进行修改操作(如add,set,remove)的时候,会复制原数组的值到创建的新数组中,并且读操作的时候不加锁,写操作会加锁
CopyOnWriteArrayList 的读操作
不加锁,每次写操作都会创建并复制新的数组,所以读数据的时候不会发生冲突,所以读操作不需要加锁,这样读操作的效率就会很高
CopyOnWriteArrayList 的写操作
加锁,会复制并创建新数组,因此开销大,数据量大的时候,在同一时刻会存在两倍大小的List大小的内存占用
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// ...CODE...
} finally {
lock.unlock();
}
}
CopyOnWriteArrayList 会出现读写不一致吗?
会,CopyOnWriteArrayList 使用的是弱一致性。
通过设计上的取舍
-
换取高性能的读操作
-
保证线程安全
-
不保证实时一致性
因此CopyOnWriteArrayList 适合读多写少的场景
代码检测
public class CopyOnWriteDemo {
public static void main(String[] args) throws InterruptedException {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
// 线程:不停读
Thread reader = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Reader: " + list);
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {}
}
});
// 线程:写入数据
Thread writer = new Thread(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException ignored) {}
list.add("C");
System.out.println("Writer added C");
});
reader.start();
writer.start();
reader.join();
writer.join();
}
}
join():让主线程等待子线程,避免出现数据不一致,或主线程结束了子线程才结束
输出结果:
Reader: [A, B]
Writer added C
Reader: [A, B, C]
Reader: [A, B, C]
Reader: [A, B, C]
Reader: [A, B, C]
显然:
写操作对应的数组是“新”数组
读操作对应的数组是“旧”数组
写完成之后就将旧数组替换成新数组
Collections.synchronizedList() 介绍
是Collections的一个包装方法,可以将List转换为线程安全的版本,对每个方法(set,get,add,remove) 都会添加一个sychronized关键字,进行同步锁,从而保证线程安全
适用场景
需要将ArrayList<>()快速转换成为线程安全的版本,以及是在简单将List转换为线程安全版本临时使用的场景
List<String> list = Collections.synchronizedList(new ArrayList<>());
缺点
缺点就是不适用于高并发的场景,因为每个操作都会使用同一个sychronized同步锁
源码所示:
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
mutex: 一般指的是this,也就是说所有的读写都是用同一把锁
源码:
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
所以Collections.synchronizedList()是不适合在高并发场景下使用的
总结
| 特性 | synchronizedList | CopyOnWriteArrayList |
|---|---|---|
| 线程安全方式 | synchronized 锁 | 写时复制(读无锁) |
| 读操作 | 加锁(慢) | 无锁(快) |
| 写操作 | 加锁(较快) | 复制数组(很慢) |
| 适用场景 | 写多读少 | 读多写少 |
| 迭代器 | 不安全,需手动 synchronized | 安全,不会抛 CME |

浙公网安备 33010602011771号