CopyOnWriteArrayList核心源码阅读

对于ArrayList来说,它的线程是不安全的。而Vector作为线程安全的list实现类,它的add、remove还是get方法都加上了synchronized锁,需要巨大的系统开销,效率低下。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。

本文将对CopyOnWriteArrayList中的核心源码进行解读,了解其工作原理及思想体现。

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

CopyOnWriteArrayList源码

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

可以看出CopyOnWriteArrayList实现了List接口,RandomAccess接口(随机访问 根据下标),Cloneable接口(可以进行克隆),Serializable接口(可序列化)

 

  //加锁 transient 不被序列化 
    final transient ReentrantLock lock = new ReentrantLock();
    //array数组 volatile 轻量级的同步机制 开销更低 同步性更差
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    //创建空列表
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

 volatile 修饰的是数组引用!简单的在原来数组修改几个元素的值,这种操作是无法发挥可见性的,必须通过修改数组内存地址

     //get获取指定下标元素
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    
    public E get(int index) {
    	//调用内部get方法
        return get(getArray(), index);
    }

get(int index)不需要加锁,因为CopyOnWriteArrayList在add/remove操作时,不会修改原数组,所以读操作不会存在线程安全问题。这其实就是读写分离的思想,只有写入的时候才加锁,复制副本来进行修改

set方法

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
            //为了确保 voliatile 的语义,所以尽管写操作没有改变数据,还是调用set方法
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

set方法会加上锁,而get方法不加锁,这个时候如果多线程正好写数据,读取的时候还是会读取到旧的数据

add方法

//从尾部增加
    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];
                // 拷贝index之前的元素到新数组,拷贝前后,元素下标不变
                System.arraycopy(elements, 0, newElements, 0, index);
                // 拷贝index之后的元素到新数组,拷贝之后,下标+1
                // 数组index处需要空出来留给新增元素
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            // 新数组替换原数组
            setArray(newElements);
        } finally {
        	// 解锁
            lock.unlock();
        }
    }

这两个add方法完成的功能不一样,但是实现步骤和原理都差不多

remove方法

 //删除(根据下标)
    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];
                // 拷贝index之前的元素到新数组,拷贝前后下标不变
                System.arraycopy(elements, 0, newElements, 0, index);
                // 拷贝index之后的元素到新数组,拷贝之后下标-1
                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;
            //原元素下标小于0 新元素下标超出长度 新元素下标小于原元素下标 抛出异常
            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];
                // 拷贝index之前的元素到新数组
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                // 拷贝index之后的元素到新数组
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
            	// 新数组替换原数组
                setArray(newElements);
            }
        } finally {
        	// 解锁
            lock.unlock();
        }
    }

Vector、ArrayList、CopyOnWriteArrayList

这三个集合类都继承List接口

1、ArrayList是线程不安全的

2、Vector是比较古老的线程安全的,但性能不行

3、CopyOnWriteArrayList在兼顾了线程安全的同时,又提高了并发性,性能比Vector要高

CopyOnWriteArrayList有什么优缺点

缺点:

1内存占用,因为写时复制的原理,所以在添加新元素的时候会复制一份,此刻内存中就会有两份对象

2、数据一致性问题,因为CopyOnWrite容器只能保证最终的数据一致性,并不能保证数据的实时性,也就是不具备原子性的效果。

3、效率低,随着数组的元素越来越多,修改的时候拷贝数组将会越来越耗时。

优点:

1、读多写少,很多时候我们的系统应对的都是读多写少的并发场景,读操作是无锁操作所以性能较高

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值