CopyOnWriteArrayList源码阅读笔记

CopyOnWriteArrayList源码阅读笔记,第1张

概述简介 ArrayList是开发中使用比较多的集合,它不是线程安全的,CopyOnWriteArrayList就是线程安全版本的ArrayList。CopyOnWriteArrayList同样是通过数组
简介

ArrayList是开发中使用比较多的集合,它不是线程安全的,copyOnWriteArrayList就是线程安全版本的ArrayList。copyOnWriteArrayList同样是通过数组实现,这个类的名字叫“copyOnWrite ”,它是在写入的时候拷贝数组,对副本进行 *** 作。


原理

copyOnWriteArrayList采用了一种读写分离的并发策略。copyOnWriteArrayList容器允许并发读,读 *** 作是无锁的,性能较高。至于写 *** 作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写 *** 作,结束之后再将原容器的引用指向新容器。示意图如下:



继承体系


通过类图,可以看到copyOnWriteArrayList的继承体系·:

实现了List,RandomAccess,Cloneable,java.io.Serializable等接口。

实现了List,提供了基础的添加、删除、遍历等 *** 作。

实现了RandomAccess,提供了随机访问的能力。

实现了Cloneable,可以被克隆。

实现了Serializable,可以被序列化。


源码分析属性
    //可重入锁,保证线程安全    final transIEnt reentrantlock lock = new reentrantlock();        //存放数据元素的数组,只能通过get/set方法访问    private transIEnt volatile Object[] array;    final Object[] getArray() {        return array;    }        final voID setArray(Object[] a) {        array = a;    }
lock:用于修改时加锁,使用transIEnt修饰表示不自动序列化。array:被使用volatile修饰表示一个线程对这个字段的修改另外一个线程立即可见。
构造方法无参构造方法:创建一个空数组
public copyOnWriteArrayList() {        setArray(new Object[0]); }
有参构造方法,参数为集合
    public copyOnWriteArrayList(Collection<? extends E> c) {        Object[] elements;         // 如果c也是copyOnWriteArrayList类型        // 那么直接把它的数组拿过来使用        if (c.getClass() == copyOnWriteArrayList.class)            elements = ((copyOnWriteArrayList<?>)c).getArray();        else {           //否则,先转换为数组            elements = c.toArray();            // c.toArray might (incorrectly) not return Object[] (see 6260652)           //  检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型            if (elements.getClass() != Object[].class)                elements = Arrays.copyOf(elements,elements.length,Object[].class);        }        setArray(elements);    }
有参构造方法,参数为数组
    //把tocopyIn的元素拷贝给当前List的数组。    public copyOnWriteArrayList(E[] tocopyIn) {        setArray(Arrays.copyOf(tocopyIn,tocopyIn.length,Object[].class));    }

add(E e)

添加一个元素到末尾

    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();        }    }

add(int index,E element)

在指定位置插入数组

 public voID add(int index,E element) {        //获取锁        final reentrantlock lock = this.lock;        //加锁        lock.lock();        try {           //旧数组            Object[] elements = getArray();            int len = elements.length;            //判断下标是否越界            if (index > len || index < 0)                throw new indexoutofboundsexception("Index: "+index+                                                    ",Size: "+len);            //新数组                                                    Object[] newElements;            int numMoved = len - index;            if (numMoved == 0)            // 如果插入的位置是最后一位            // 那么拷贝一个n+1的数组,其前n个元素与旧数组一致                newElements = Arrays.copyOf(elements,len + 1);            else {                // 如果插入的位置不是最后一位               // 那么新建一个n+1的数组                newElements = new Object[len + 1];                //拷贝旧数组[0,……index-1]下标的元素                System.arraycopy(elements,newElements,index);                //拷贝旧数组的其余元素到新数组[index+1,……length+1],刚好空出了index下标位置                System.arraycopy(elements,index,index + 1,numMoved);            }            //将插入的元素放到index下标位置            newElements[index] = element;            //给array赋值            setArray(newElements);        } finally {           //释放锁            lock.unlock();        }    }

写入 *** 作:

在上面添加元素的 *** 作中,都进行了加锁的 *** 作拷贝一个新数组,长度等于原数组长度加1,并把原数组元素拷贝到新数组中把新数组赋值给当前对象的array属性,覆盖原数组
remove(int index)

根据下标位置移除数据元素:

    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)            // 如果移除的是最后一位            // 那么直接拷贝一份n-1的新数组,最后一位就自动删除了                setArray(Arrays.copyOf(elements,len - 1));            else {              // 如果移除的不是最后一位             // 那么新建一个n-1的新数组                Object[] newElements = new Object[len - 1];                // 将前index个元素拷贝到新数组中                System.arraycopy(elements,index);                // 将index后面(不包含)的元素往前挪一位               // 这样正好把index位置覆盖掉了,相当于删除了                System.arraycopy(elements,numMoved);                setArray(newElements);            }            return oldValue;        } finally {            //释放锁            lock.unlock();        }    }

删除 *** 作:删除 *** 作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写 *** 作,需要加锁。


get(int index)
    public E get(int index) {        return get(getArray(),index);    }       final Object[] getArray() {        return array;    }        private E get(Object[] a,int index) {        return (E) a[index];    }

获取 *** 作:获取 *** 作属于读 *** 作,直接通过数组下标获取数据元素,没有加锁,所以保证了性能。


size()
    public int size() {       //返回数组长度        return getArray().length;    }

和ArrayList不同,查看ArrayList源码阅读笔记,可以发现ArrayList中是有size属性的,这是因为ArrayList数组的长度实际是要大于集合的大小的。copyOnWriteArrayList每次修改都是拷贝一份正好可以存储目标个数元素的数组,所以不需要size属性,直接返回数组长度即可。


总结

copyOnWriteArrayList使用reentrantlock重入锁加锁,保证线程安全;

copyOnWriteArrayList的写 *** 作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能相对低下;

copyOnWriteArrayList的读 *** 作支持随机访问,时间复杂度为O(1);

copyOnWriteArrayList采用读写分离的思想,读 *** 作不加锁,写 *** 作加锁,且写 *** 作占用较大内存空间,所以适用于读多写少的场合;

copyOnWriteArrayList只保证最终一致性,不保证实时一致性;



纸上得来终觉浅,绝知此事要躬行。



参考:

【1】:【死磕 Java 集合】— CopyOnWriteArrayList源码分析
【2】:CopyOnWriteArrayList实现原理及源码分析

总结

以上是内存溢出为你收集整理的CopyOnWriteArrayList源码阅读笔记全部内容,希望文章能够帮你解决CopyOnWriteArrayList源码阅读笔记所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1154151.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-01
下一篇 2022-06-01

发表评论

登录后才能评论

评论列表(0条)

保存