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

一、CopyOnWriteArrayList介绍

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

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

二、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;
}

三、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。每次对集合进行修改都会对互斥锁加锁,防止其他线程并发修改集合,每次集合修改操作都是先在新数组(即集合内部数组副本)上进行然后在修改完成之后重置数组引用到新数组,在这过程中集合读取的仍然是集合内部数组,因此集合修改操作不会阻塞集合读取。

 

四、CopyOnWriteArrayListe使用示例

转载于:https://my.oschina.net/zhangyq1991/blog/1920047

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值