【Java基础】谈谈集合.CopyOnWriteArrayList
本篇博客介绍CopyOnWriteArrayList类,读完本博客你将会了解:
- 什么是COW机制;
- CopyOnWriteArrayList的实现原理;
- CopyOnWriteArrayList的使用场景。
经过之前的博客介绍,我们知道ArrayList是线程不安全的。要实现线程安全的List,我们可以使用Vector,或者使用Collections工具类将List包装成一个SynchronizedList。其实在Java并发包中还有一个CopyOnWriteArrayList可以实现线程安全的List。
在开始之前先贴一段概念
如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。
实现原理
Vector这个类是一个非常古老的类了,在JDK1.0的时候便已经存在,其实现安全的手段非常简单所有的方法都加上synchronized关键字,这样保证这个实例的方法同一时刻只能有一个线程访问,所以在高并发场景下性能非常低。
SynchronizedList是java.util.Collections中的一个静态内部类,其实现安全的手段稍微有一点优化,就是把Vector加在方法上的synchronized关键字,移到了方法里面变成了同步块而不是同步方法从而把锁的范围缩小了,另外,SynchronizedList中的方法不全都是同步的,比如获取迭代器方法listIterator()就不是同步的。下面看下CopyOnWriteArrayList怎么实现线程安全的。
CopyOnWriteArrayList这个类就比较特殊了,对于写来说是基于重入锁互斥的,对于读操作来说是无锁的。还有一个特殊的地方,这个类的iterator是fail-safe的,也就是说是线程安全List里面的唯一一个不会出现ConcurrentModificationException异常的类。
看下CopyOnWriteArrayList的成员变量:
//重入锁保写操作互斥
final transient ReentrantLock lock = new ReentrantLock();
//volatile保证读可见性
private transient volatile Object[] array;
下面再看下添加元素的代码逻辑
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();//加锁
try {
Object[] elements = getArray();//读取原数组
int len = elements.length;
//构建一个长度为len+1的新数组,然后拷贝旧数据的数据到新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//把新加的数据赋值到最后一位
newElements[len] = e;
// 替换旧的数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
先获得锁,然后拷贝元素组并将新元素加入(添加的元素可以是null),再替换掉原来的数组。我们会发现这种实现方式非常不适合频繁修改的操作。CopyOnWriteArrayList的删除和修改的操作的原理也是类似的,这边就不贴代码了。
最后看下读操作
//直接获取index对应的元素
public E get(int index) {return get(getArray(), index);}
private E get(Object[] a, int index) {return (E) a[index];}
从以上的增删改查中我们可以发现,增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。为什么增删改中都需要创建一个新的数组,操作完成之后再赋给原来的引用?这是为了保证get的时候都能获取到元素,如果在增删改过程直接修改原来的数组,可能会造成执行读操作获取不到数据。
遍历时不用加锁的原因
常用的方法实现我们已经基本了解了,但还是不知道为啥能够在容器遍历的时候对其进行修改而不抛出异常。(其实这是一种fail-safe机制)
// 1. 返回的迭代器是COWIterator
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
// 2. 迭代器的成员属性
private final Object[] snapshot;
private int cursor;
// 3. 迭代器的构造方法
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
// 4. 迭代器的方法...
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
//.... 可以发现的是,迭代器所有的操作都基于snapshot数组,而snapshot是传递进来的array数组
到这里,我们应该就可以想明白了!CopyOnWriteArrayList在使用迭代器遍历的时候,操作的都是原数组!
CopyOnWriteArrayLis的缺点
- 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。
- 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。
使用场景
整体来说CopyOnWriteArrayList是另类的线程安全的实现,但并一定是高效的,适合用在读取和遍历多的场景下,并不适合写并发高的场景,因为数组的拷贝也是非常耗时的,尤其是数据量大的情况下。
总结
稍微总结下:
- CopyOnWriteArrayList基于可重入锁机制,增删改操作需要加锁,读操作不需要加锁;
- CopyOnWriteArrayList适合用在读取和遍历多的场景下,并不适合写并发高的场景;
- 基于fail-safe机制,不会抛出CurrentModifyException;
- 另外CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数组是一个快照。
其他网友的总结:
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为了避免Copy出N个副本出来,导致并发写。
但是,CopyOnWriteArrayList中的读方法是没有加锁的。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,当然,这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的,并非强一致性。
所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器。而Vector在读写的时候使用同一个容器,读写互斥,同时只能做一件事儿。
参考
【Java基础】谈谈集合.CopyOnWriteArrayList的更多相关文章
- java基础技术集合面试【笔记】
java基础技术集合面试[笔记] Hashmap: 基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键(除了不同步和允许使用 null 之外,Ha ...
- java基础-Map集合
java基础-Map集合 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Map集合概述 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它 ...
- 第6节:Java基础 - 三大集合(上)
第6节:Java基础 - 三大集合(上) 本小节是Java基础篇章的第四小节,主要介绍Java中的常用集合知识点,涉及到的内容包括Java中的三大集合的引出,以及HashMap,Hashtable和C ...
- Java基础之 集合体系结构(Collection、List、ArrayList、LinkedList、Vector)
Java基础之 集合体系结构详细笔记(Collection.List.ArrayList.LinkedList.Vector) 集合是JavaSE的重要组成部分,其与数据结构的知识密切相联,集合体系就 ...
- 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!
前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...
- Java基础--说集合框架
版权所有,转载注明出处. 1,Java中,集合是什么?为什么会出现? 根据数学的定义,集合是一个元素或多个元素的构成,即集合一个装有元素的容器. Java中已经有数组这一装有元素的容器,为什么还要新建 ...
- JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API
森林森 一份耕耘,一份收获 博客园 首页 新随笔 联系 管理 订阅 随笔- 397 文章- 0 评论- 78 JAVA基础学习day16--集合三-Map.HashMap,TreeMap与常用A ...
- 《回炉重造 Java 基础》——集合(容器)
整体框架 绿色代表接口/抽象类:蓝色代表类. 主要由两大接口组成,一个是「Collection」接口,另一个是「Map」接口. 前言 以前刚开始学习「集合」的时候,由于没有好好预习,也没有学好基础知识 ...
- java基础之集合长度可变的实现原理
首先我们要明白java中的集合Collection,List,ArrayList之间的关系: ArrayList是具体的实现类,实现了List接口 List是接口,继承了Collection接口 Li ...
随机推荐
- 即时聊天APP(一)
最新写了一个即时聊天的安卓Demo,是基于Bmob后端开发的app,由于Bmob有较大局限性,因此,我并没有按照开发文档来进行开发,只是简单写了一个基本的文字聊天,以后有时间我会自己写一个带服务端的即 ...
- OAuth2.0 RFC 6749 中文
英文原版:https://tools.ietf.org/html/rfc6749 转自:https://github.com/jeansfish/RFC6749.zh-cn 一.简介 在传统的客户端- ...
- java数据结构——数组(Array)
数据结构+算法是我们学习道路上的重中之重,让我们一起进步,一起感受代码之美! /** * 让我们从最基本的数据结构——数组开始吧 * 增.删.改.查.插.显示 */ public class Seql ...
- JavaScript DOM 编程艺术
最近把JavaScript DOM 编程艺术这本书看完了,觉得这本书很好 深入浅出地展示了渐进增强.平稳退化.结构和样式分离等编程思想,我对书中重要的知识进行了梳理总结. 一.网页 二.JavaScr ...
- Python学习-迭代器、生成器
一.迭代器 1. 可迭代对象 我们知道字符串.列表.元组.字典.集合都可以使用for语句进行循环遍历,然后输出每一个元素,这些都是可迭代对象. 检查对象是否是可迭代对象可以用两种方式去判断: (1)使 ...
- 手把手教你Pytest+Allure2.X定制报告详细教程,给自己的项目量身打造一套测试报告-02(非常详细,非常实用)
简介 前边一篇文章是分享如何搭建pytest+Allure的环境,从而生成一份精美的.让人耳目一新的测试报告,但是有的小伙伴或者童鞋们可能会问,我能不能按照自己的想法为我的项目测试结果量身打造一份属于 ...
- Maven 梳理-安装配置
项目构建过程包括[清理项目]→[编译项目]→[测试项目]→[生成测试报告]→[打包项目]→[部署项目]这几个步骤,这六个步骤就是一个项目的完整构建过程. 下载后解压 配置环境变量 F:\jtDev ...
- springboot + mybatis + mycat整合
1.mycat服务 搭建mycat服务并启动,windows安装参照. 系列文章: [Mycat 简介] [Mycat 配置文件server.xml] [Mycat 配置文件schema.xml] [ ...
- asp.net core 使用Mysql和Dapper
序曲:学习编程最好的方式就是敲代码,没有比这个更好的方法,哪怕你看了上百G的视频,都不如你自己敲几行代码更为有效.还有要记得敲完代码然后写一篇随笔来记录一下你所学所想. 大家都知道,.netcore是 ...
- Jetpack系列:应用内导航的正确使用方法
今天小编要分享的还是Android Jetpack库的基本使用方法,本篇介绍的内容是Jetpack Navigation组件,让我们一起学习,为完成年初制定的计划而努力吧! *** 组件介绍 导航,是 ...