CopyOnWriteArrayList源码分析

简介

线程安全的List,使用Java锁和数组副本实现并发的控制。字面上意思 写时拷贝:当往集合写数据时拷贝一个新的副本进行写,过后替换原来的数组,这个过程为同步操作。总体就是:同步写,并发读,读写分离。

类图

在这里插入图片描述
继承体系与ArrayList大致相同

属性

    /** 控制并发的锁 */
    final transient ReentrantLock lock = new ReentrantLock();

    /** 仅通过getArray / setArray访问数组 */
    private transient volatile Object[] array;
    
    final Object[] getArray() {
        return array;
    }
    final void setArray(Object[] a) {
        array = a;
    }

构造方法

// 够着一个空数组
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();
        // 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));
}

写 add、set

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; // 新数组最后一位设置为新数据
        setArray(newElements); // 替换旧数组
        return true;
    } finally {
        lock.unlock();
    }
}

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 {
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

读 get

// 不加锁 直接读
private E get(Object[] a, int index) {
    return(E) a[index];
}

public E get(int index) {
    return get(getArray(), index);
}

遍历

static final class COWIterator<E> implements ListIterator<E> {
    /** 数组快照 */
    private final Object[] snapshot;
    /** 下一个遍历的元素索引  */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    public boolean hasPrevious() {
        return cursor > 0;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }

    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor-1;
    }

    /**
     * 遍历时只允许读,不支持修改
     */
    public void remove() {
        throw new UnsupportedOperationException();
    }

    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    public void add(E e) {
        throw new UnsupportedOperationException();
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        Object[] elements = snapshot;
        final int size = elements.length;
        for (int i = cursor; i < size; i++) {
            E e = (E) elements[i];
            action.accept(e);
        }
        cursor = size;
    }
}

总结

核心思想,写时复制,读写分离,适合读远多于写的场景
写时复制:写的时候拷贝一个新的副本,性能不高
读写分离:读可以并发读,写时需要同步

原文地址:https://www.cnblogs.com/paper-man/p/13284610.html