CopyOnWriteArrayList有感

·  阅读 42

看过了线程安全的map:ConcurrentHashMap,现在来看一下线程安全的ArrayList:CopyOnWriteArrayList,看CopyOnWriteArrayList是如何保证读写线程安全的

先来看看成员变量: 一把重入锁,一个volatile数组

    final transient ReentrantLock lock = new ReentrantLock();
    private transient volatile Object[] array;
复制代码
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();
        }
    }
复制代码

可以看到这边对add操作加了锁,elements是原数组,newElements是新数组且数组长度比原数组多1,把新元素放到新数组最后一个位置,然后把新数组赋给Array。加锁可以保证,在多线程写的情况下,只有一个线程能获取锁来写,从而保证了写的线程安全

再来看看读

public E get(int index) {
        return get(getArray(), index);
    }
   
final Object[] getArray() {
        return array;
    } 
private E get(Object[] a, int index) {
        return (E) a[index];
    }    
    
复制代码

代码很简洁啊,也没看到锁啊,CAS什么的。 假如一个线程1正在add新元素,此时Array指向的还是老的数组,那此时线程2来get元素,还是从老数组中读。如果已经把新的数组赋给Array,由于volatile的可见性线程2可以看到最新的数组,线程2就从新数组中取值。CopyOnWriteArrayList通过这种写时复制的机制保证了读写安全

CopyOnWriteArrayList写操作加可重入锁性能上不如基于CAS的写, CopyOnWriteArrayList的读有点最终一致性的意思,可能在读写并发情景下出现读到是旧数据,但是最终数据会是新数据。

分类:
后端
标签:
分类:
后端
标签: