【线程安全的List】CopyOnWriteArrayList的原理及使用

1、原理

  • CopyOnWriteArrayList是一个线程安全的ArrayList

  • 如果一段并发程序,读操作明显多于写操作的话,那么使用CopyOnWriteArrayList的性能会比Vector更高

  • CopyOnWriteArrayList的实现原理就是读写分离,它对所有的写操作都使用ReentrantLock来加锁,对所有的读操作都不加锁,那它是怎么保证线程安全性问题的呢?

  • CopyOnWriteArrayList在写操作的时候,都会将list中的数组copy一份作为缓存,然后对该缓存中的数组进行操作(此时若有其他线程过来读的话,那么该线程读的还是原先没有被修改过的数组,若有其他线程过来写的话,那么该线程会因为ReentrantLock的原因被锁在外面。),操作完毕后再将list中的数组地址引用指向修改后的新数组地址。

  • 由CopyOnWriteArrayList的原理我们可以看出,我们每次往list里面写数据的时候,数组都需要重新copy一份,所以CopyOnWriteArrayList不需要实现像ArrayList一样的扩容机制,初始创建时让list中的数组长度为0,我们每次add元素的时候,只需要对新数组长度进行加1操作即可,所以CopyOnWriteArrayList实现起来相对还是比ArrayList简单的。

    // 构造方法
    public CopyOnWriteArrayList() {
    setArray(new Object[0]);
    }
    
    // 添加一个元素
    public boolean add(E e) {
        
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        
            // 创建一个新数组,长度+1
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            
            // 将元素添加到新数组的末端,并将新数组赋值给本对象中的array
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            // 解锁
            lock.unlock();
        }
    }
    
    // 获取一个元素,没加锁
    public E get(int index) {
        return get(getArray(), index);
    }
    
  • 所以我们可以看出,如果是读操作十分频繁的话,那么多线程下使用CopyOnWriteArrayList的性能基本上跟ArrayList差不多了。但如果是写操作十分频繁的话,建议还是不要使用CopyOnWriteArrayList了,因为它会造成数组的不断扩容及复制,十分耗性能。这其实就跟我们数据库读写分离的原理是一样的,如果写操作很多的话,那么主从库就会不断的执行复制操作,消耗性能。但如果是读操作多的话,由于该库只用于读,所以不会发生数据库事务锁,效率就会比一般的单库查询快很多。

2、使用

  • CopyOnWriteArrayList的使用和ArrayList差不多,这里没什么好说的

猜你喜欢

转载自blog.csdn.net/qq906627950/article/details/79479067