Java集合-CopyOnWriteArrayList源码解析-JDK1.8

CopyOnWriteArrayList简介

CopyOnWriteArrayList和ArrayList一样是一个动态数组。而与ArrayList不同的是,它是线程安全的。

建议先阅读 ArrayList源码分析 ,再回来看此文会Soeasy哦!

CopyOnWriteArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

  1. List提供了添加、删除、修改、遍历等功能。

  2. RandmoAccess提供了随机访问功能

  3. Cloneable提供了可以被克隆的功能

  4. Serializable提供了序列化的功能

CopyOnWriteArrayList的属性

 /**实现线程安全的锁对象*/	
    final transient ReentrantLock lock = new ReentrantLock();	

	
    /** 元素缓冲区,volatile保证多线程下始终读取到最新的数据*/	
    private transient volatile Object[] array;

上方的lock对象就是CopyOnWriteArrayList实现线程安全的秘诀。    

对于Java中的锁有疑问的同学可以参考此文章:  Java中的锁

CopyOnWriteArrayList的构造方法

    	
     public CopyOnWriteArrayList() {	
        setArray(new Object[0]);	
    }	

	

	
    public CopyOnWriteArrayList(Collection c) {	
        Object[] elements;	
        if (c.getClass() == java.util.concurrent.CopyOnWriteArrayList.class)	
            elements = ((CopyOnWriteArrayList)c).getArray();	
        else {	
            elements = c.toArray();	
            // c.toArray might (incorrectly) not return Object[] (see 6260652)	
            if (elements.getClass() != Object[].class)	
                elements = Arrays.copyOf(elements, elements.length, Object[].class);	
        }	
        setArray(elements);	
    }	

	
    public CopyOnWriteArrayList(E[] toCopyIn) {	
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));	
    }	
    final void setArray(Object[] a) {	
        array = a;	
    }

三个构造方法最后都是调用的setArray方法完成的初始化。

CopyOnWriteArrayList的方法

接下来我们就以CopyOnWriteArrayList的几个比较经典的方法来看一下它是如何设计的。

首先是添加方法:

 /**	
     * 添加	
     */	
    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();	
        }	
    }	

	
    /**	
     * 指定索引添加	
     */	
    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)	
                newElements = Arrays.copyOf(elements, len + 1);	
            else {	
                newElements = new Object[len + 1];	
                System.arraycopy(elements, 0, newElements, 0, index);	
                System.arraycopy(elements, index, newElements, index + 1,	
                        numMoved);	
            }	
            newElements[index] = element;	
            setArray(newElements);	
        } finally {	
            lock.unlock();	
        }	
    }	
    final Object[] getArray() {	
        return array;	
    }	
    final void setArray(Object[] a) {	
        array = a;	
    }

可以看的,进行添加之前首先是要先获得锁对象才继续进行的操作(包括修改和删除),只有这样才能保证线程安全。

接着看添加的逻辑

新建一个数组,接着将通过getArray()方法获取到的原始的数组拷贝到新数组中,然后将新增数据也添加到新数组中;最后将新数组赋值给原先的数组。

接下来看删除操作:

  /**	
     * 删除	
     */	
    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();	
        }	
    }

同添加方法相同的逻辑,先获取锁,然后通过copy数组方式进行删除操作。

接下来修改方法,修改的时候也使用到了查询方法:

/**	
     * 替换指定索引的元素	
     */	
    public E set(int index, E element) {	
        final ReentrantLock lock = this.lock;	
        lock.lock();	
        try {	
            Object[] elements = getArray();	
            E oldValue = get(elements, index);	

	
            if (oldValue != element) {	
                int len = elements.length;	
                Object[] newElements = Arrays.copyOf(elements, len);	
                newElements[index] = element;	
                setArray(newElements);	
            } else {	
                // Not quite a no-op; ensures volatile write semantics	
                setArray(elements);	
            }	
            return oldValue;	
        } finally {	
            lock.unlock();	
        }	
    }	
     /**	
     * get方法	
     */	
    public E get(int index) {	
        return get(getArray(), index);	
    }	
     @SuppressWarnings("unchecked")	
    private E get(Object[] a, int index) {	
        return (E) a[index];	
    }	

	
   	

第一步同样是加锁,接着会有一个判断,看要修改的索引位置的元素是否相同,不相同则继续通过copy数组的方式进行替换,最后使用setArray()方法更新。

看了CopyOnWriteArrayList的增删改查方法你就应该明白一件事,这哥们除了查询、增删改都很慢呀。

与ArrayList相比,CopyOnWriteArrayList最值得我们注意的地方就是:

  1. 增删改操作必须获取锁之后才能进行,操作完毕释放锁其他操作才可以继续执行

  2. get元素是使用volatile修饰的,可以保证多线程之间的数据更新同步

 
鉴于篇幅有限,本篇文章仅列出上方部分代码,CopyOnWriteArrayList完整源码解析请 点击下方“阅读原文”查看!!!

640?wx_fmt=gif

不得不看

1.SpringCloud系列博客汇总

2.Java多线程面试必备基础知识汇总

640?wx_fmt=jpeg

万水千山总是情,点个 “在看” 行不行!!!

640?wx_fmt=png 

你可能感兴趣的:(Java集合-CopyOnWriteArrayList源码解析-JDK1.8)