Java并发List容器—CopyOnWriteArrayList源码解析

CopyOnWriteArrayList源码解析

1.概述

我们知道ArrayList是线程不安全的,其存在一个古老的线程安全的Vector,但是由于Vector效率太低(方法都加了synchronzed),在JDK1.5时Doug Lea提供了一个效率较高的线程安全的CopyOnWriteArrayList,其实现了阻塞写操作而不阻塞读操作,写时拷贝原数组写完替换原数组,实现了读写分离。

2.继承体系

  • CopyOnWriteArrayList实现了List,提供了基础的添加、删除、遍历等操作。
  • CopyOnWriteArrayList实现了Cloneable,可以被克隆。
  • CopyOnWriteArrayList实现了Serializable,可以被序列化。

在这里插入图片描述

3.核心结构

3.1属性

    //全局的Lock锁,增删改时都需要先加锁,保证线程安全。
	final transient ReentrantLock lock = new ReentrantLock();
	
	//底层的数组
    private transient volatile Object[] array;

3.2核心小方法

    //获取底层的数组array
	final Object[] getArray() {
        return array;
    }

	//重新为array赋值。
    final void setArray(Object[] a) {
        array = a;
    }

3.3构造器

    /*
     * 无参构造器,调用了setArray()方法为array赋值为一个长度为0的数组
     */
	public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

	
	/*
	 * 深拷贝一个toCopyIn数组赋值给array。
	 */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

	/*
	 * 传入一个集合。
	 */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        //最终为array赋值。
        Object[] elements;
        //如果c是CopyOnWriteArrayList类型的,直接将elements引用c内部的数组
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        //c不是CopyOnWriteArrayList类型
        else {
            //toArray()方法是深拷贝了一个c内部的数组,交给elements引用。
            elements = c.toArray();
            if (c.getClass() != ArrayList.class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        //为array赋值。
        setArray(elements);
    }

4.核心方法

4.1add(E e)方法

public boolean add(E e) {
    //获取全局锁
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取内部数组
        Object[] elements = getArray();
        int len = elements.length;
		//拷贝元素原数组的元素到新数组中,且新数组的长度比原数组多1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //直接将元素插入到最后
        newElements[len] = e;
        //将新数组为array赋值
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

4.2add(int index, E element)

public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取原数组
        Object[] elements = getArray();
        //获取长度
        int len = elements.length;
        // 检查是否越界 (index可以等于len 即在数组最后添加元素)
        if (index > len || index < 0){
            //抛异常
        }
        //声明新数组    
        Object[] newElements;
        
        /*
         *  numMoved表示本次插入操作需要移动的元素个数
         *  比如len = 5 ,index = 3 
         *  即本次在index = 3的位置插入元素,那么index = 3 index = 4的两个元素需要
         *  向后移动,即需要移动两个元素。
         */
        int numMoved = len - index;
        
        //len == index,说明本次需要在数组末尾添加元素
        if (numMoved == 0)
			//直接深拷贝一个长度为len + 1的数组。
            newElements = Arrays.copyOf(elements, len + 1);
        
        //本次不是在数组末尾添加元素
        else {
            //先初始化一个长度为len + 1的数组
            newElements = new Object[len + 1];
            //拷贝原数组index之前的元素到新数组(从新数组的0位置开始)
            System.arraycopy(elements, 0, newElements, 0, index);
			
            //拷贝index之后的元素(包括index位置)到新数组的从index + 1开始,刚好将
            //新数组的index位置空了出来,后面直接将元素插入到index位置。
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        // 将元素直接插入在index处
        newElements[index] = element;
        //为array赋值。
        setArray(newElements);
    } finally {
        // 释放锁
        lock.unlock();
    }
}

4.3remove(int index)

    /*
     * 删除指定索引位置的元素。
     */
	public E remove(int index) {
        //获取全局锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取内部数组
            Object[] elements = getArray();
            //获取长度
            int len = elements.length;
           	
            /*
             * get()方法(非同步方法),获取数组指定索引位置的元素。
             */
            E oldValue = get(elements, index);
            
            //本次删除需要移动元素的个数
            int numMoved = len - index - 1;
            
            /*
             * numMoved = 0,说明本次删除的是最后一个元素,
             * 直接拷贝一个长度为len - 1的数组即可(变向的删除了最后一个元素)
             */
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            //删除的非最后一个元素
            else {
                //创建一个长度为len - 1的数组
                Object[] newElements = new Object[len - 1];
                //将原数组[0 - index)的元素拷贝到新数组中
                System.arraycopy(elements, 0, newElements, 0, index);
                //将原数组(index, len - 1]的元素拷贝到新数组中,刚好删除了index位置
                //的元素
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                //重新为array赋值
                setArray(newElements);
            }
            //返回删除的元素
            return oldValue;
        } finally {
            //解锁
            lock.unlock();
        }
    }


//					||
//					||
//					\/
	//获取a数组的index位置上的元素。
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

4.4set(int index, E element)

  	/*
  	 * 修改指定位置的元素
  	 */
	public E set(int index, E element) {
        //获取全局锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            //获取原index位置的元素
            E oldValue = get(elements, index);
			//要修改的值不等于原值时,进行修改
            if (oldValue != element) {
                int len = elements.length;
                //深拷贝一份原数组
                Object[] newElements = Arrays.copyOf(elements, len);
                //重新为index位置的元素赋值
                newElements[index] = element;
                //重新为array赋值
                setArray(newElements);
            } else {
                //要修改的值等于原值,不需要修改,直接将elements还赋值给array即可。
                setArray(elements);
            }
            return oldValue;
        } finally {
            //解锁
            lock.unlock();
        }
    }

4.5get()

    /*
     *  非同步方法,读时不加锁 
     */
	public E get(int index) {
        //获取数组的index位置的元素。
        return get(getArray(), index);
    }

//					||
//					||
//					\/
	//获取a数组的index位置上的元素。
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

4.6size()

	/*
	 * 获取元素个数 
	 * 注意:CopyOnWriteArrayList没有size属性,因为每次删除添加都是拷贝	                          		                 		  																		
	 * 一份刚好可以存储目标元素个数的数组,所以不需要size属性,数组的长度就是集合的大小,
	 * 而不像ArrayList数组的长度实际是要大于集合的大小的
	 */
	public int size() {
	   //直接返回数组的长度。
       return getArray().length;
	}

5.总结

  • CopyOnWriteArrayList使用了JUC包下的ReentrantLock实现了增删改方法的同步,保证了线程安全。
  • CopyOnWriteArrayList的写(增删改)操作都要先加锁,然后拷贝一份原数组,修改新数组最终将新数组替换原数组,空间复杂度为O(n),性能比较低。
  • CopyOnWriteArrayList的读操作的时间复杂度为O(1).
  • CopyOnWriteArrayList采用读写分离的思想,读操作不加锁写操作加锁,且写操作占用较大内存空间,所以适用于读多写少的场合
  • CopyOnWriteArrayList只保证最终一致性,不保证实时一致性
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shstart7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值