Java源码学习--并发集合类01--01--CopyOnWriteArrayList

CopyOnWriteArrayList

  • 特点
    1. 线程安全的,多线程直接使用,无须加锁
    2. 通过锁+数组拷贝+volatile关键字保证了线程安全
    3. 每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作完成之后再赋值回去
  • 整体步骤:对数组进行操作的时候
    1. 加锁
    2. 从原数组拷贝出新数组
    3. 在新数组上进行操作,并把新数组赋值给数组容器
1. 继承体系
  • 代码
    public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        ...
    }
    
  • RandomAccess接口,是一个标志接口,表明实现这个接口的List集合是支持快速随机访问的,同时,在jdk源码中,还说明了,此注解表示的类,使用for通过索引访问的速度要比使用迭代器使用要快一点,建议使用for+索引的方式来进行查询访问
2. 具体方法
2.1 新增
  • 代码
    1. 添加到尾部
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //1, 得到原来是数组
            Object[] elements = getArray();
            int len = elements.length;
            //2. 拷贝的新的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //3. 将新的元素添加到新的数组中
            newElements[len] = e;
            //4. 替换旧的数组
            setArray(newElements);
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }
    
    1. 在内部任意位置增加
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        //1. 加锁
        lock.lock();
        try {
    
            //2. 获取之前的数组
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
    
            //3. 计算出需要移动的元素个数
            int numMoved = len - index;
    
            //4. 正好是最后一位
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
    
                //将原数组的[0,index)移动到新数组中,index为移动的个数
                System.arraycopy(elements, 0, newElements, 0, index);
    
                //将原数组的[index,length)数组移动到新的数组中,其中numMoved为个数
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    
    1. 设置数组
    final void setArray(Object[] a) {
        array = a;
    }
    
  • 注意点
  1. 总体的流程为
    1. 使用lock进行枷锁
    2. 获取旧是数组
    3. 生成新的数组,将旧的数组复制到新的数组
    4. 将底层的数组设置为新的数组
  2. 数组为volatile修改,为的是修改数组引用时,不同的线程可以得知变化,刷新本地缓冲。
2.2 删除
  • 代码
    1.删除指定数组索引:可以看出和新增的逻辑是相同的
    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();
        }
    }
    
    1. 批量删除
    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) {
    
                    //将temp临时数组赋值到底层的数组
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
    
  • 注意点
  1. 对于批量删除,使用了空间换时间的操作,将不需要删除的元素保存到一个临时数组中,最后判断是否删除了元素,如果个数不同,将临时数组当成底层数组实现了批量删除操作
2.3 迭代器遍历
  • 代码
    1. 迭代器遍历
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    static final class COWIterator<E> implements ListIterator<E> {
        //保存当前需要迭代的数组,使用final赋值,表示,一旦赋值就不能改变
        private final Object[] snapshot;
    
        private int cursor;
    
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
    }
    
  • 注意点
    1. 即使CopyOnWriteArrayList在迭代过程中,发生了变动了,也不会抛出ConcurrentModificationException异常,因为每次迭代的时候都是使用的旧的数组,不会遍历到新数组的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值