并发包下的CopyOnWriteArrayList如何实现安全的?

  Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器

这样做的好处:实现对CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作。所以CopyOnWrite容器也是一种读写分离的思想。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteArrayListCopyOnWriteArraySet

CopyOnWriteArrayList相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用ReentrantLock实现,允许多个线程并发读取,但只能有一个线程写入。

我们看看它的源码,看它的各个方法如何实现安全的。

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    final transient ReentrantLock lock = new ReentrantLock();

    private transient volatile Object[] array;

    final Object[] getArray() {
        return array;
    }

    final void setArray(Object[] a) {
        array = a;
    }


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


//有参构造方法(传入一个Connection集合)
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
//如果传入的这个对象本身就是CopyOnWriteArrayList类型的
//获取到这个CopyOnWriteArrayList的底层数组,赋给当前这个对象的数组
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
//否则就先将对象转化成数组
//如果不是Object类型数组
//转化为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);
        }
//将当前的Object数组赋值给属性
        setArray(elements);
    }

//按照下标赋值
    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();
        }
    }


//添加数据
    public boolean add(E e) {
//加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
//获取到原来的数组 复制到一个新数组,并给新数组长度+1
            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;
//判断 数组长度-传入的下标 是否等于0
//如果等于0 代表要添加的位置为数组最末位
            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();
        }
    }


//根据下标删除元素
    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();
        }
    }


//根据范围删除元素
    void removeRange(int fromIndex, int toIndex) {
//加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
//判断输入的起始结束下标是否越界
            if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
                throw new IndexOutOfBoundsException();
//计算删掉这个范围的元素后新数组的长度
            int newlen = len - (toIndex - fromIndex);
            int numMoved = len - toIndex;
//如果删除的结束位置是数组末尾
            if (numMoved == 0)
//只将删除的起始位置之前的元素 复制进新数组
                setArray(Arrays.copyOf(elements, newlen));
            else {
//如果是删除的是中间的一段
                Object[] newElements = new Object[newlen];
//将删除范围前后的两段元素 复制进新数组
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                setArray(newElements);
            }
        } finally {
            lock.unlock();
        }
    }

//求交集(让当前对象集合 = 将传入的集合和当前的集合对象取交集 )
    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;
// 如果当前的数组长度不是0(代表有元素 需要判断是否有交集)
            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));
// 返回true成功
                    return true;
                }
            }
// 如果原数组为空 直接返回失败
            return false;
        } finally {
            lock.unlock();
        }
    }

   
// 清空集合
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
// 创建一个空数组赋值给 当前集合的数组
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }

    // 添加集合到对应下标
    public boolean addAll(int index, Collection<? extends E> c) {
        Object[] cs = c.toArray();
        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);
// 如果传入的集合是空的 直接返回失败 
            if (cs.length == 0)
                return false;
// 定义新数组
            int numMoved = len - index;
            Object[] newElements;
// 如果插入的位置是末尾 创建一个长度为原数组加传入集合长度 并将原数组复制进新数组
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + cs.length);
            else {
// 如果插入位置不是末尾
// 创建新数组 并将插入位置前后的元素分别复制进新数组 并给插入位置留下要插入的空长度
                newElements = new Object[len + cs.length];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index,
                                 newElements, index + cs.length,
                                 numMoved);
            }
// 将要插入的数组插入对应位置
            System.arraycopy(cs, 0, newElements, index, cs.length);
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值