博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CopyOnWriteArrayList源码解析(1)
阅读量:6592 次
发布时间:2019-06-24

本文共 2870 字,大约阅读时间需要 9 分钟。

此文已由作者赵计刚授权网易云社区发布。

欢迎访问,了解更多网易技术产品运营经验。

注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析。

1、对于CopyOnWriteArrayList需要掌握以下几点

  • 创建:CopyOnWriteArrayList()

  • 添加元素:即add(E)方法

  • 获取单个对象:即get(int)方法

  • 删除对象:即remove(E)方法

  • 遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去做遍历

注:CopyOnWriteArrayList是一个线程安全,读操作时无锁的ArrayList。

 

2、创建

public CopyOnWriteArrayList()

使用方法:

List
 list = new CopyOnWriteArrayList
();

相关源代码:

    private volatile transient Object[] array;//底层数据结构    /**     * 获取array     */    final Object[] getArray() {        return array;    }    /**     * 设置Object[]     */    final void setArray(Object[] a) {        array = a;    }    /**     * 创建一个CopyOnWriteArrayList     * 注意:创建了一个0个元素的数组     */    public CopyOnWriteArrayList() {        setArray(new Object[0]);    }

注意点:

  • 设置一个容量为0的Object[];ArrayList会创造一个容量为10的Object[]

 

3、添加元素

public boolean add(E e)

使用方法:

list.add("hello");

源代码:

    /**     * 在数组末尾添加元素     * 1)获取锁     * 2)上锁     * 3)获取旧数组及其长度     * 4)创建新数组,容量为旧数组长度+1,将旧数组拷贝到新数组     * 5)将要增加的元素加入到新数组的末尾,设置全局array为新数组     */    public boolean add(E e) {        final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁        lock.lock();//上锁        try {            Object[] elements = getArray();//获取当前的数组            int len = elements.length;//获取当前数组元素            /*             * Arrays.copyOf(elements, len + 1)的大致执行流程:             * 1)创建新数组,容量为len+1,             * 2)将旧数组elements拷贝到新数组,             * 3)返回新数组             */            Object[] newElements = Arrays.copyOf(elements, len + 1);            newElements[len] = e;//新数组的末尾元素设成e            setArray(newElements);//设置全局array为新数组            return true;        } finally {            lock.unlock();//解锁        }    }

注意点:

  • Arrays.copyOf(T[] original, int newLength)该方法在ArrayList中讲解过

疑问:

  • 在add(E)方法中,为什么要重新定义一个ReentrantLock,而不直接使用那个定义的类变量锁(全局锁)

    • 答:事实上,按照他那样写,即使是在add、remove、set中存在多个引用,最后也是一个实例this.lock,所以不管你在add、remove、set中怎样去从新定义一个ReentrantLock,其实add、remove、set中最后使用的都是同一个锁this.lock,也就是说,同一时刻,add/remove/set只能有一个在运行。这样讲,就是说,下边这段代码完全可以做一个修改。修改前的代码:

          public boolean add(E e) {        final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁        lock.lock();//上锁

      修改后的代码:

          public boolean add(E e) {        //final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁        this.lock.lock();//上锁
  • 根据以上代码可知,每增加一个新元素,都要进行一次数组的复制消耗,那为什么每次不将数组的元素设大(比如说像ArrayList那样,设置为原来的1.5倍+1),这样就会大大减少因为数组元素复制所带来的消耗?

 

4、获取元素

public E get(int index)

使用方法:

list.get(0)

源代码:

    /**     * 根据下标获取元素     * 1)获取数组array     * 2)根据索引获取元素     */    public E get(int index) {        return (E) (getArray()[index]);    }

注意点:

  • 获取不需要加锁

疑问:在《分布式Java应用:基础与实践》一书中作者指出:读操作会发生脏读,为什么?

从类属性部分,我们可以看到array数组是volatile修饰的,也就是当你对volatile进行写操作后,会将写过后的array数组强制刷新到主内存,在读操作中,当你读出数组(即getArray())时,会强制从主内存将array读到工作内存,所以应该不会发生脏读才对呀!!!

 补:volatile的介绍见《》,链接如下:

更多网易技术、产品、运营经验分享请。

相关文章:

【推荐】 

转载地址:http://bycio.baihongyu.com/

你可能感兴趣的文章
Mysql cluster数据备份和恢复
查看>>
我的友情链接
查看>>
查看nginx/apache/php/mysql编译参数
查看>>
怎样将lib设为源文件夹
查看>>
分布式文件系统之MooseFS----部署
查看>>
Linux下用cronolog切割Tomcat日志并删除指定天数前的日志记录
查看>>
python打印10以内的奇数和偶数
查看>>
记一次nagios故障及解决
查看>>
supertab for vim
查看>>
漫谈递归和迭代
查看>>
我的友情链接
查看>>
确保云中虚拟安全白皮书连载一:虚拟化安全含义
查看>>
第八周作业
查看>>
development note 2013.11.23
查看>>
OpenGL-第一篇 OPENGL基础
查看>>
Hadoop 部署之 Hadoop (三)
查看>>
Centos7 iptables/netfilter 详解
查看>>
Apache的mpm工作模式
查看>>
Linux中变量$#,$@,$0,$1,$2,,$3,$4,$5,$6,$7,$8,$9,$*,$$,$?的含义
查看>>
悲哀的技术人:一个38岁老售前的求职困惑
查看>>