jdk源码分析之CopyOnWriteArrayList

CopyOnWriteArrayList的原理

CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的数组,在新的数组上面修改,然后将新数组赋值给旧数组的引用,并通过volatile 保证其可见性,通过Lock保证并发写。

底层数据结构

private volatile transient Object[] array;
final Object[] getArray() {
        return array;
final void setArray(Object[] a) {
        array = a;
    }   

底层采用Object数组存储数据
数组使用volatile修饰保证可见性,不读缓存直接读写内存
数组使用private修饰限制访问与,只能通过getter和setter访问
数组使用transient修饰,表示序列化时忽略此字段(自己定制序列化操作)

定制序列化操作

因为Object数组被transient修饰,因此需要CopyOnWriteArrayList类自己制定序列化方案
方法writeObject和readObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是采用默认的序列化方案。ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。
在两个方法的开始处,调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为被transient修饰的字段定制序列化。

     /**
     * Saves the state of the list to a stream (that is, serializes it).
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        s.defaultWriteObject();

        Object[] elements = getArray();
        // Write out array length
        s.writeInt(elements.length);

        // Write out all elements in the proper order.
        for (Object element : elements)
            s.writeObject(element);
    }

先调用s.defaultWriteObject()对非transient修饰的字段进行序列化操作
然后序列化写入数组的长度,再循环写入数组的元素

     /**
     * Reconstitutes the list from a stream (that is, deserializes it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        // bind to new lock
        resetLock();

        // Read in array length and allocate array
        int len = s.readInt();
        Object[] elements = new Object[len];

        // Read in all elements in the proper order.
        for (int i = 0; i < len; i++)
            elements[i] = s.readObject();
        setArray(elements);
    }

反序列化的时候先调用s.defaultReadObject()恢复没有被transient修饰的字段
然后为反序列化得到的CopyOnWriteArrayList对象创建一把新锁
接着恢复数组的长度,根据数组的长度创建一个Object的数组
然后恢复数组的每一个元素

读操作不加锁

    public E get(int index) {
        return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

读操作是直接通过getArray方法获取Object数组,然后通过下标index直接访问数据。读操作并没有加锁,也没有并发的带来的问题,因为写操作是加锁写数组的副本,写操作成功将副本替换为原数据,这也是写时复制名字的由来。

加锁写副本

 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();
        }
    }

set方法先通过lock加锁,然后获取index位置的旧数据,供最后方法返回使用

E oldValue = get(elements, index);

接着创建数组的副本,在副本上进行数据的替换

Object[] newElements = Arrays.copyOf(elements, len);

Arrays.copyOf(elements, len)方法将会从elements数组复制len个数据创建一个新的数组返回
然后在新数组上进行数据替换,然后将新数组设置为CopyOnWriteArrayList的底层数组

newElements[index] = element;
setArray(newElements);

最后在finally块里边释放锁

特定位置添加数据

  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();
        }
    }

添加数据和替换数据类似,先加锁,然后数组下标检查,接着创建数组副本,在副本里边添加数据,将副本设置为CopyOnWriteArrayList的底层数组

猜你喜欢

转载自blog.csdn.net/shihui512/article/details/51498399
今日推荐