CopyOnWriteArrayList的源码分析


ArrayList和CopyOnWriteArrayList

我们知道多线程并发时ArraysList是线程不安全,而CopyOnWriteArrayList是线程安全,那么CopyOnWriteArrayList是如何保证线程安全的呢?通过以下常用的方法说明:

1.Se()方法

首先两个set()方法的作用是一样的都是替换指定位置的值,但CopyOnWriteArrayList中的set()方法实现ReentrantLock加锁机制,这就是为什么CopyOnWriteArrayList是线程安全。CopyOnWriteArrayList中通过getArray()方法得到原来的数组可理解为oldArray[ ];再通过get(elements, index)方法将原来该下标位置的值取出即为oldValue;然后判断要修改的值是否与原来的值相等,如果相等则将原数组重新放回,如果不相等则进行元素替换通过Arrays.copyOf(elements, len)方法复制新的数组,将指定位置的值进行修改;最后将修改完的数组放回并释放锁

ArrayList的set()方法
	public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
CopyOnWriteArrayList的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
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }    

2.add()方法

同理,CopyOnWriteArrayList中的add()方法是实现ReentrantLock锁机制也是线程安全。也是通过getArray()得到原来数组;再通过Arrays.copyOf(elements, len + 1)方法复制原来的数组oldArray[ ],但比原数组的长度多1,是因为将要添加的元素采用尾插法存储至数组尾部;最后依旧将新数组放回并释放锁

ArrayList的add()方法
	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
CopyOnWriteArrayList的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();
        }
    }

3.add()添加指定位置的元素

通过getArray()得到原来数组;先判断指定的下标是否越界或不规范,如果不符合则抛出下标越界的异常,如果符合;再判断该元素移动多少个元素,如果发现移动0个元素那么证明要添加的元素应为数组的尾部,那么直接与添加元素的原理相同,如果不等于0那么证明该元素被添加在数组的中间某个位置,则通过System.arraycopy(elements, 0, newElements, 0, index)方法将该下标之前的元素复制出来至新数组、再通过System.arraycopy(elements, index, newElements, index + 1,numMoved)将该下标后半部分元素复制至新数组,再将元素添加至该下标;最后将新数组放回并释放锁

ArrayList的add()方法
	 public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
CopyOnWriteArrayList的add()方法
	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];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

4.Remove()方法

通过getArray()得到原来数组;获取到指定下标的元素、先判断删除的元素是否为数组中最后一个元素,如果是数组中最后一个元素则直接复制len-1个元素至新数组再将新数组放回,如果不是最后一个通过 System.arraycopy(elements, 0, newElements, 0, index)方法将删除元素下标之前的元素复制至新数组再通过System.arraycopy(elements, index + 1, newElements, index, numMoved)方法将删除元素下标之后的元素复制至新数组;最后将新数组放回并释放锁

ArrayList的remove()
	public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
CopyOnWriteArrayList的add()方法
	 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();
        }
    }

总结

  • 多线程并发时,ArrayList是线程不安全,而CopyOnWriteArrayList是线程安全就是因为CopyOnWriteArrayList实现ReentrantLock锁机制,也正因为锁机制CopyOnWriteArrayList的速度没有ArrayList的速度快。
  • ArrayList是会进行扩容,CopyOnWriteArrayList每次都是复制数组无需扩容,但大量的数组复制也是很消耗内存及cpu的性能。
  • CopyOnWriteArrayList由于只在写时加锁,读时无锁,导致多线程读数据时数据并一定是最新的数据,导致数据无法做到实时性,也常用于写少读多的场景。
  • 还有一些方法都类似与上面的原理,自己可以找找源代码再查看。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MM呦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值