Java多线程系列之线程安全集合CopyOnWriteArrayList

1、CopyOnWriteArrayList介绍

    CopyOnWriteArrayList在Java中一般做为ArrayList的线程安全实现,他继承自List并实现了RandomAccess、Cloneable、java.io.Serializable在支持全部List操做的基础上也支持随机访问、拷贝和序列化,功能与ArrayList基本相同。java

    CopyOnWriteArrayList底层存储结构是一个动态对象数组,不一样于ArrayList,内部维护了一个ReenTrantLock锁lock,在每次进行写操做前加锁,采用了写入时复制的思想,在每次对CopyOnWriteArrayList进行修改、删除、添加等写操做时都会事先在内部建立一个数组副本,在这个副本上进行写操做,此时读操做不须要加锁,且读取的仍然是CopyOnWriteArrayList内部对象数组。当CopyOnWriteArrayList读取过程当中有其余线程修改集合时读取的数据多是旧数据,而且CopyOnWriteArrayList每次写操做时都须要进行数组复制,在内存中须要占用两份数组对象内存,若是写操做频繁或者数组对象较大可能触发频繁的GC这无疑是十分低效的,所以CopyOnWrite应该只适用于读操做远高于写操做,对数据实时性要求不高且数组对象较小的场合,例如缓存。数组

2、CopyOnWriteArrayList数据结构

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //序列号
    private static final long serialVersionUID = 8673264195747942595L;

    //互斥锁,保证集合写操做的线程安全
    final transient ReentrantLock lock = new ReentrantLock();

    //存储集合元素的数组,只能经过set和get方法直接访问
    private transient volatile Object[] array;
    
    private static final sun.misc.Unsafe UNSAFE;
    
    private static final long lockOffset;
}

3、CopyOnWriteArrayList源码分析

1 - 构造函数

//无参构造函数,建立一个空的对象数组并让内部动态数组引用array指向它
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    //构造函数入参是Collection容器对象
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        //若入参c是CopyOnWriteArrayList类实例那么直接让当前 
        //CopyOnWriteArrayList实例内部数组引用array指向入参c内部数组array
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        //若入参c不是CopyOnWriteArrayList实例那么调用它的toArray方法返回包含内部全部元素的对象数组,
        //若是数组类型不是Object类型则还须要进行转化
        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);
    }

    //入参为数组的构造函数
    //将E[]数组转化为Object[]数组并让内部对象数组引用指向新数组
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

2 - 添加元素

    咱们经过add(E e)方法了解一下CopyOnWriteArrayList如何实如今集合中添加元素。下面是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);
            //在新数组中插入指定新元素e
            newElements[len] = e;
            //让集合对象数组引用指向建立的新对象数组
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    add(E e)方法在操做以前会先获取当前集合对象的互斥锁,进行加锁,此时其余线程没法经过获取该锁修改该集合,分析源码可知,CopyOnWriteArrayList添加新元素操做是在原数组的拷贝数组上作的,在拷贝数组上插入新元素e以后直接将集合内部的数组引用指向它,因为内部数组array由volatile修饰所以是多线程可见的,元素添加操做未完成前其余线程的读操做不用阻塞读取的是原对象数组,而在元素添加成功数组引用改变以后读取的是最新的对象数组数据。

3 - 删除元素

    CopyOnWriteArrayList删除元素内部咱们经过分析remove(Object o)方法源码来了解下,如下是remove(Object 0)方法源码:安全

public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);
        return (index < 0) ? false : remove(o, snapshot, index);
    }

    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

    前面的步骤没什么好说的,先获取集合内部对象数组,遍历它获取指定元素o的数组下标,若在数组中不存在元素o则返回-1,最后若是返回的元素索引index小于0表示元素不存在删除失败,不然调用remove(o,snapshot,index)方法删除元素,咱们进入该方法源码:数据结构

private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取集合当前内部保存元素数组
            Object[] current = getArray();
            //获取集合当前内部数组长度
            int len = current.length;
            //snapshot不等于current即在调用方法调用本方法以后到加锁以前有其余线程修改了集合
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    //集合结构发生修改且元素未被删除
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                //若待删除元素索引index大于数组长度len,则该元素已经被其余线程删除,方法结束返回false
                if (index >= len)
                    return false;
                //获取到被删除元素o对应的数组下标直接退出最外层条件statement
                if (current[index] == o)
                    break findIndex;
                //遍历内部数组下标index以后部分,获取指定元素o的下标
                index = indexOf(o, current, index, len);
                //若获取不到结束返回false
                if (index < 0)
                    return false;
            }
            //建立数组
            Object[] newElements = new Object[len - 1];
            //填充原数组被删除元素前面部分到新数组
            System.arraycopy(current, 0, newElements, 0, index);
            //继续填充原数组被删除元素后面部分到新数组中
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            //重置内部数组引用
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    由于remove(Object o, Object[] snapshot, int index)方法调用方remove(Object o)未作线程同步处理,因此在调用方调用本方法到加锁以前可能有其余线程修改集合结构,为了保证集合中数据的最终一致性,本方法在内部同步代码块基于加锁前传入的内部元素数组和当前元素数组对比,判断集合是否被修改过,若集合未被修改或者元素未被删除,那么建立新数组将当前内部对象数组剩余元素数据复制到新数组,让集合内部数组引用从新指向新数组。多线程

    这里博主认为静态方法indexOf其实提供的是数组中获取对应元素下标的功能,做为CopyOnWriteArrayList类的私有静态方法其实并不合适不符合程序设计思想,应该进行能力抽象单独抽取出来放在一个类里面。并发

4 - 设置元素

public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取集合内部数组
            Object[] elements = getArray();
            //获取对应位置元素oldValue
            E oldValue = get(elements, index);
            //指定元素element不等于旧元素oldValue
            if (oldValue != element) {
                //数组长度
                int len = elements.length;
                //建立原数组副本
                Object[] newElements = Arrays.copyOf(elements, len);
                //设置指定位置元素值为指定元素element
                newElements[index] = element;
                //集合数组引用指向新数组
                setArray(newElements);
            }
            //指定索引位置旧元素等于新元素element 
            else {
                //数组引用仍指向原数组对象
                setArray(elements);
            }
            return oldValue;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

5 - 其余成员方法

//返回集合元素个数
    public int size() {
        return getArray().length;
    }

    //判断集合是否为空,是返回true
    public boolean isEmpty() {
        return size() == 0;
    }
    //判断集合中是否包含元素o
    public boolean contains(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length) >= 0;
    }

    //查询集合中第一个匹配元素o的索引下标
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }

    //返回集合内部数组中下标index以后第一个匹配元素e的下标
    public int indexOf(E e, int index) {
        Object[] elements = getArray();
        return indexOf(e, elements, index, elements.length);
    }

    //返回集合中最后一个匹配元素o的索引下标
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1);
    }
    //拷贝函数
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
            CopyOnWriteArrayList<E> clone =
                (CopyOnWriteArrayList<E>) super.clone();
            clone.resetLock();
            return clone;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

    //返回集合内部对象数组的一个副本
    public Object[] toArray() {
        Object[] elements = getArray();
        return Arrays.copyOf(elements, elements.length);
    }
    //相似将集合元素填充到一个数组中并返回,注意当入参数组a的容量小于集合元素个数时不会将元素数据拷贝到数组a中而是
    //直接返回方法内部建立的填充了集合元素数据的新数组
    public <T> T[] toArray(T a[]) {
        Object[] elements = getArray();
        int len = elements.length;
        if (a.length < len)
            return (T[]) Arrays.copyOf(elements, len, a.getClass());
        else {
            System.arraycopy(elements, 0, a, 0, len);
            if (a.length > len)
                a[len] = null;
            return a;
        }
    }
   
    //获取集合索引index处元素
    public E get(int index) {
        return get(getArray(), index);
    }

    //在指定位置index处插入指定新元素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)
                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();
        }
    }
    //删除集合中指定索引位置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)
                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();
        }
    }

    //插入指定元素e若是集合中已经存在该元素不插入,返回操做结果
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

    //插入指定元素若是该元素在集合中已经存在操做失败,该方法经过加锁实现线程同步,经过方法参数snapshot(方法调用前
    //内部数组对象)与当前数组对象不相等肯定说明集合修改过
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

    //判断当前集合是否包含指定容器c全部元素,是返回true否返回false
    public boolean containsAll(Collection<?> c) {
        Object[] elements = getArray();
        int len = elements.length;
        for (Object e : c) {
            if (indexOf(e, elements, 0, len) < 0)
                return false;
        }
        return true;
    }

    //删除集合中容器c中的全部元素
    public boolean removeAll(Collection<?> c) {
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    //集合除了保留指定容器c中的全部元素,删除其余元素
    public boolean retainAll(Collection<?> c) {
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    //往当前集合CopyOnWriteArrayList中插入指定容器c中的全部元素,原集合已经存在的元素则不重复插入
    public int addAllAbsent(Collection<? extends E> c) {
        Object[] cs = c.toArray();
        if (cs.length == 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            int added = 0;
            // uniquify and compact elements in cs
            for (int i = 0; i < cs.length; ++i) {
                Object e = cs[i];
                if (indexOf(e, elements, 0, len) < 0 &&
                    indexOf(e, cs, 0, added) < 0)
                    cs[added++] = e;
            }
            if (added > 0) {
                Object[] newElements = Arrays.copyOf(elements, len + added);
                System.arraycopy(cs, 0, newElements, len, added);
                setArray(newElements);
            }
            return added;
        } finally {
            lock.unlock();
        }
    }

    //清空集合中的全部元素,实质就是让内部数组引用指向一个空数组对象
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }

6 - CopyOnWriteArrayList实现线程安全原理总结

    经过粗略分析了CopyOnWriteArrayList源码咱们应该清楚做为一个线程安全容器它实现线程安全是基于一个ReentrantLock互斥锁lock和内部维护的一个volatile对象数组array。每次对集合进行修改都会对互斥锁加锁,防止其余线程并发修改集合,每次集合修改操做都是先在新数组(即集合内部数组副本)上进行而后在修改完成以后重置数组引用到新数组,在这过程当中集合读取的仍然是集合内部数组,所以集合修改操做不会阻塞集合读取。

 

4、CopyOnWriteArrayListe使用示例

相关文章
相关标签/搜索