Java CopyOnWriteArrayList的源码解析

之前分析过ArrayList的源码,发现ArrayList是线程不安全的,现在来解析CopyOnWriteArrayList的源码,看看它是怎么实现线程安全的。

创建

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

这是默认创建的方法,来看看源码。

	//1
	private transient volatile Object[] array;

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

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

        和ArrayList一样,CopyOnWriteArrayList其实是用数组来存储元素的。不同的是CopyOnWriteArrayList的数组多了volatile关键字(代码1)。
        首先要明白volatile的作用是什么,总的来说volatile有三大特性。
        1.保证可见性
        2.不保证原子性
        3.禁止指令重排序
        简单地说一下可见性,线程修改主内存的变量并不是直接在上面写的,而是先在线程自己的工作内存里创建一个副本,修改完在写会主内存。只有一个线程操作这个变量并没有什么问题,但多线程就不一样了,各线程并不能保证及时知道这个变量被其他线程改动过,也就是说变量的可见性得不到保证(对volatile还很陌生的朋友可以先看看这篇文章)。
        回到源码(代码2),可以看到array是指向一个大小为0的数组的,也就是说在默认情况下CopyOnWriteArrayList的初始容量为0。再看看其他的创建方法。

public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        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);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

        代码都很简单,就是让array指向元素数组的一个副本。仔细的朋友会发现,不同于ArrayList,CopyOnWriteArrayList并没有设置初始大小的构造方法。

添加元素

list.add("str1");

        这是常用的方法,看看源码。

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //1.1
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //2
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
        	//1.2
            lock.unlock();
        }
}

        看到代码1.1和1.2,分别是上锁和释放锁,这就是和ArrayList最大的区别,CopyOnWriteArrayList增加了锁机制。
        现在来说说原子性,原子性是指一个操作是不可以再被分割的。比如,某原子操作分为A、B和C三个步骤,ABC要么全做完,要么全不做,不可以先做A再做D,然后再回去做BC。上面提到过volatile并不能保证操作的原子性。
        从源码中可以看出CopyOnWriteArrayList是用lock和unlock(显式锁)来实现原子性的。那为什么不直接在add方法上添加synchronized(隐式锁)关键字呢?如果只是考虑原子性,synchronized方法完全可以胜任,但是synchronized加在方法上面意味着锁的作用范围是整个方法。而lock和unlock的可以根据需要锁定部分的代码块,相比之下灵活许多。
        例如一个方法有3行代码是A->B->C,其中B和C必须是原子操作。如果采用synchronized方法的方式,那么线程运行到A时就可能会被阻塞了。而采用lock和unlock的方式把BC锁起来,那么线程可以运行完A再阻塞。这么一来就会发现,synchronized方法使用方便但会对性能有所损耗。
        保证原子性就讲完了,那么add方法是怎么实现添加元素的呢?看到刚刚的代码2。创建一个新的数组,长度为当前长度加1,插入元素,再让array指向这个数组。
        ArrayList每次添加元素都需要进行剩余容量检查。CopyOnWriteArrayList就不同了,每次调用add方法就得创建一个新的数组,也就没有容量检查和扩容的说法了。

删除元素

list.remove(0);

这是删除操作的基本用法,看看源码。

public E remove(int index) {
        final ReentrantLock lock = this.lock;
        //1.1
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
            	//2
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            	//3
                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 {
        	//1.2
            lock.unlock();
        }
    }

        同样,为了保证删除的原子性,在代码1.1和1.2分别使用了lock和unlock。代码2和代码块3也是一样创建一个删除后的数组,再让array指向它。

清空元素

public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }

        一样的,通过lock和unlock实现同步,再让array指向大小为0的数组(注意区别大小为0的数组和null)。

参考文章

java volatile的特性
java synchronized的使用方法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值