Java 经典面试题:聊一聊 JUC 下的 CopyOnWriteArrayList
Java 经典面试题:聊一聊 JUC 下的 CopyOnWriteArrayList
ArrayList 是我们常用的工具类之一,但是在多线程的情况下,ArrayList 作为共享变量时,并不是线程安全的。主要有以下两个原因:
1、 ArrayList 自身的 elementData、size、modCount 在进行操作的时候,都没有加锁;
2、这些变量没有被 volatile 修饰,在多线程的情况下,对这些变量操作可能会出现值被覆盖的情况;
如果我们想在多线程情况下使用 ArrayList 怎么办?有以下几种办法:
使用 Collections.SynchronizedList ;
使用 JUC 下的 CopyOnWriteArrayList;
先来看看 SynchronizedLis,Collections 其实就是对 ArrayList 进行了一个加锁包装,这个从源码中可以看出;
...部分源码,完整源码请查看 JDK 源码...
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
对于 Collections.SynchronizedList 比较简单,就是锁包装了一下,就不多说了~
CopyOnWriteArrayList 也是 JUC 下面的一个并发容器类。不知道你发现没有,但凡你常用的集合类,在 JUC 下基本上都可以找到一个并发类,比如 hashMap 有对应的 ConcurrentHashMap。
CopyOnWriteArrayList 跟 ArrayList 在整体架构上并没有什么区别,底层都是基于数组实现的。不同的地方大概有两点:
底层数组被 volatile 关键字修饰;
对数组进行数据变更时加锁;
CopyOnWriteArrayList 的加锁操作跟 Collections.SynchronizedList 简单的加锁还不一样,CopyOnWriteArrayList 中的加锁过程还是非常值得学习的。CopyOnWriteArrayList 的加锁过程,大概可以概括为以下四步:
1、加锁;
2、从原数组中拷贝出新数组;
3、在新数组上进行操作,并把新数组赋值给数组容器;
4、解锁;
结合源码来深入了解 CopyOnWriteArrayList 的并发实现,我们选择 ArrayList 最简单的将元素新增数组尾部的操作来分析实现过程,源码如下:
/**
- Appends the specified element to the end of this list.
* - @param e element to be appended to this list
- @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 获取锁,注意这是全局锁 final ReentrantLock lock = this.lock; // 加锁操作 lock.lock(); try { // 获取数组 Object[] elements = getArray(); int len = elements.length; // 将数组内容拷贝到新数组中 Object[] newElements = Arrays.copyOf(elements, len + 1); // 对新数组操作 newElements[len] = e; // 变更底层数组的引用 setArray(newElements); return true; } finally { // 解锁 lock.unlock(); }
}
CopyOnWriteArrayList 就是通过加锁来说实现容器安全的,可能你会有疑问,为什么引入一个新数组,数组的拷贝还是消耗时间的,直接在原数组上操作不就好了吗?。主要原因有以下两点:
volatile 关键字修饰的是数组,如果我们简单的在原来数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。
在新的数组上进行拷贝,对老数组没有任何影响,只有新数组完全拷贝完成之后,外部才能访问到,降低了在赋值过程中,老数组数据变动的影响。比如经典的 ConcurrentModificationException 异常问题。
其他的新增方法就自己去查看源码了,相差不多,基本上是一样的。对数组的删除跟新增都是差不多,不同的地方是在删除了时候,赋值给新数组时会出现不同的选择策略。我把源码贴上:
public E remove(int index) {
final ReentrantLock lock = this.lock; // 加锁 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); // 先计算出要移动的问题 int numMoved = len - index - 1; // 根据移动的位置选择策略 if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { //解锁 lock.unlock(); }
}
CopyOnWriteArrayList 还有其他的方法,在这里我就不过多介绍了。根据你们自己的疑问去扒一扒 CopyOnWriteArrayList 的源码就知道了,总体来说 CopyOnWriteArrayList 并不难,甚至感觉比 ArrayList 要简单。
总结一下:CopyOnWriteArrayList 是安全的并发容器,有以下两个特点:
1、对数组的写操作加锁,读操作不加锁;
2、通过加锁 + 数组拷贝+ volatile 来保证线程安全;
原文地址https://www.cnblogs.com/jamaler/p/12843126.html

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
推荐一款Python数据可视化神器
推荐一款Python数据可视化神器 前言在日常工作中,为了更直观的发现数据中隐藏的规律,察觉到变量之间的互动关系,人们常常借助可视化帮助我们更好的给他人解释现象,做到一图胜千文的说明效果。 在Python中,常见的数据可视化库有: matplotlib 是最常见的2维库,可以算作可视化的必备技能库,由于matplotlib是比较底层的库,api很多,代码学起来不太容易。seaborn 是建构于matplotlib基础上,能满足绝大多数可视化需求。更特殊的需求还是需要学习matplotlib。上述两个库都是静态的可视化库,大多数做过前端Web开发的同学都用到过Echarts.js库,它是一款前端可视化的JS库、功能非常之强大。在使用之前,需要导入js库到项目中。对于平时用Python较多的同学而言,如果每次实现可视化功能(特别是一些小需求),都需要引用js库显然不太方便,于是就在想有没有Python与Echarts结合的轮子。答案是肯定的,在Github中就有一个国人开发的一个Echarts与Python结合的轮子:Pyecharts,它不仅很好的兼容了web项目,而且可以做到可视化的动...
- 下一篇
python数据统计分析
python数据统计分析 常用函数库scipy包中的stats模块和statsmodels包是python常用的数据分析工具,scipy.stats以前有一个models子模块,后来被移除了。这个模块被重写并成为了现在独立的statsmodels包。 scipy的stats包含一些比较基本的工具,比如:t检验,正态性检验,卡方检验之类,statsmodels提供了更为系统的统计模型,包括线性模型,时序分析,还包含数据集,做图工具等等。 小样本数据的正态性检验(1) 用途 夏皮罗维尔克检验法 (Shapiro-Wilk) 用于检验参数提供的一组小样本数据线是否符合正态分布,统计量越大则表示数据越符合正态分布,但是在非正态分布的小样本数据中也经常会出现较大的W值。需要查表来估计其概率。由于原假设是其符合正态分布,所以当P值小于指定显著水平时表示其不符合正态分布。 正态性检验是数据分析的第一步,数据是否符合正态性决定了后续使用不同的分析和预测方法,当数据不符合正态性分布时,我们可以通过不同的转换方法把非正太态数据转换成正态分布后再使用相应的统计方法进行下一步操作。 (2) 示例 123456...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题