Java并发包中的并发list只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制数组(快照)上进行的,也就是使用了写时复制策略。
写时复制(CopyOnWrite,简称COW)思想是计算机程序涉及领域中的一种优化策略。其核心思想是,如果多个调用者(Callers)同时要求相同的资源(如内存或者磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用的副本给调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他调用者都是透明的。
此做法主要的优点是如果调用者没有修改资源,就不会有副本被创建,因此多个调用者只是读取操作时可以共享同一份资源。
CopyOnWriteArrayList的实现原理:
在CopyOnWriteArrayList的类图中,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改。
弱一致性的迭代器:
所谓弱一致性是指返回迭代器后,其他线程对list的增删改查对迭代器是不可见的
// 演示多线程下迭代器的弱一致性结果
public class copylist {
private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
arrayList.add("hello");
arrayList.add("alibaba");
arrayList.add("welcome");
arrayList.add("to");
arrayList.add("hengzhou");
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
// 修改list中下标为1的元素为ali
arrayList.set(1, "ali");
// 删除元素
arrayList.remove(2);
arrayList.remove(3);
}
});
// 保证在修改线程启动前获取迭代器
Iterator<String> itr = arrayList.iterator();
// 启动线程
threadOne.start();
// 等待子线程执行完毕
threadOne.join();
while(itr.hasNext()) {
System.out.println(itr.next());
}
}
}
执行程序:
hello
alibaba
welcome
to
hengzhou
Process finished with exit code 0
- 从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭代器弱一致性的体现。需要注意的是,获取迭代器的操作必须在子线程操作之前进行。
CopyOnWrite的应用场景:
CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单中,如果在,则提示不能搜索。实现代码如下:
public class BlackListServiceImpl {
// 初始化大小,避免扩容开销
private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean> (1000);
public static boolean isBlackList(String id) {
return blackListMap.get(id) == null ? false : true;
}
public static void addBlackList(String id) {
blackListMap.put(id,Boolean.TRUE);
}
// 批量添加黑名单
public static void addBackList(Map<String, Boolean> ids) {
blackListMap.putAll(ids);
}
}
- 使用CopyOnWriteMap需要注意两件事情:
- 减少扩容开销,根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销
- 使用批量添加,因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数
CopyOnWrite的缺点:
除了上面所提到的弱一致性问题,还存在一个内存占用的问题。
因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。
总结:
CopyOnWriteArrayList使用写时复制的策略来保证list的一致性,而获取-修改-写入三步操作并不是原子性的,所以在增删改的过程中都是用了独占锁,来保证某个时间只有一个线程能对list数组进行修改。另外CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对List的修改是不可见的,迭代器遍历的数组是一个快照。