Java多线程14:CopyOnWriteArrayList的详细介绍——学习方腾飞Java并发编程的艺术

CopyOnWriteArrayList

读写锁,线程安全的ArrayList。它具有以下特性:
1、它最适合于读远多写操作,需要在遍历期间防止线程间的冲突。
2、它是线程安全的。
3、因为读写锁的一些操作需要复制整个基础数组,因此对于add()、remove()、set()等操作开销很大,因此应避免此类操作。
4、使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。

源码

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
     /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

可以看出:
1、CopyOnWriteArrayList实现了List接口,因此它是一个队列
2、CopyOnWriteArrayList包含了成员lock。每一个CopyOnWriteArrayList都和一个互斥锁lock绑定,通过lock,实现了对CopyOnWriteArrayList的互斥访问。
3、CopyOnWriteArrayList包含了array[] 数组,这说明CopyOnWriteArrayList和List<>一样,是通过数组实现的。

volatile Object[] array 和 lock

CopyOnWriteArrayList通过内部的“volatile数组”(array)来保持数据。当增加修改删除数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile数组”。由于它在增加修改删除数据时,都会新建数组,这部分开销很大,所以涉及到这些操作时,CopyOnWriteArrayList的效率很低。但进行遍历查找的话,效率比较高。因此适用于读多于写的情况。
CopyOnWriteArrayList的“线程安全”是通过volatile和互斥锁来实现的。首先“volatile数组”保存数据可以保证,一个线程操作时总能看到其它线程对该volatile变量最后的写入,读取到的数据总是最新的。然后CopyOnWriteArrayList通过互斥锁来保护数据。在增加修改删除数据时,会先“获取互斥锁”,修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”。从而达到写时的安全。

常见函数的源码分析

构造函数

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

    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-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));
    }
final Object[] getArray() {
        return array;
    }

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

构造函数没什么说的,有参的都是给array数组赋值。

添加 add(E e)

public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }
public void add(int index, E element) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException(outOfBounds(index, 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);
        }
    }

说明:
1、add(E e)操作是通过synchronized代码块来实现锁,保证每次只有一个线程操作。
2、add(E e)的流程:首先新建一个数组,然后将原volatile数组的数据拷贝到新数组中,然后E e添加到新数组中,最后,将新数组赋值给volatile数组。
注意一下:
本文安装的是java1.10,而之前看的是1.7版本中,使用的是ReentrantLock锁对象,原因,我觉得是后面版本对于synchronized的优化使得synchronized更方便快捷。
1.10中synchronized块中的lock对象,

final transient Object lock = new Object();

获取 get(int index)

public E get(int index) {
        return elementAt(getArray(), index);
    }
    static <E> E elementAt(Object[] a, int index) {
        return (E) a[index];
    }

说明:
get比较简单,就返回对应index位置的数据就好

更新 set(int index, E element)

public E set(int index, E element) {
        synchronized (lock) {
            Object[] elements = getArray();
            E oldValue = elementAt(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;
        }
    }

说明:
1、set的操作流程:首先就是拿到原来的数组,查看一下原来index位置上的值,判断若是和原来的数据一样,就直接不用copy直接setArray返回修改原来的值就好,若是真的修改为不同的值,会先copy一个原来的数组,然后把对应位置的值设置为新值,其实就是数组操作,然后setArray。
2、注意这里设置值也加了互斥锁,并且也copy了数组,为什么呢?因为叫CopyOnWriteArrayList啊!

删除 remove(int index)

public E remove(int index) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = elementAt(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;
        }
    }

说明:
1、remove(int index)的的操作流程,首先判断如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行提取之前的数组,且不需要新建数组。若不是删除最后一个元素,则会新建数组,然后分段将删除位置之前和之后的数据copy到新数组,然后将新数组赋值给”volatile数组“。
2、remove(int index)也要获取互斥锁。

iterator() 遍历

public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

iterator()会返回COWIterator对象

举个例子

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author fitz.bai
 */
public class MyThread18 {
    private static List<String> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) {
        new MyThread("a").start();
        new MyThread("b").start();
    }

    static class MyThread extends Thread {

        public MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            int i = 0;
            while (i++ < 6) {
                String val = Thread.currentThread().getName() + "-" + i;
                list.add(val);
                printAll();
            }
        }

        private void printAll() {
            String value = null;
            Iterator iter = list.iterator();
            while (iter.hasNext()) {
                value = (String) iter.next();
                System.out.print(value + ", ");
            }
            System.out.println();
        }
    }
}
// 运行结果
a-1, a-1, 
b-1, 
a-1, a-1, b-1, b-1, a-2, 
a-2, a-1, b-2, 
b-1, a-1, a-2, b-1, b-2, a-2, a-3, 
b-2, a-1, a-3, b-1, b-3, 
a-2, a-1, b-2, b-1, a-3, a-2, b-3, b-2, a-4, 
a-3, a-1, b-3, b-1, a-4, a-2, b-4, 
b-2, a-3, b-3, a-4, a-1, b-4, a-5, 
b-1, a-1, a-2, b-1, b-2, a-3, a-2, b-3, b-2, a-4, a-3, b-4, b-3, a-5, a-4, b-5, 
b-4, a-1, a-5, b-1, b-5, a-6, 
a-2, b-2, a-3, b-3, a-4, b-4, a-5, b-5, a-6, b-6, 

若是AraayLIst则会报错java.util.ConcurrentModificationException

猜你喜欢

转载自blog.csdn.net/qq_22798455/article/details/81489187