List集合线程安全之CopyOnWriteArrayList

CopyOnWriteArrayList 类图

在这里插入图片描述

CopyOnWriteArrayList 类的思想

CopyOnWrite 简称 COW,根据名字来看就是写入时复制。意思就是大家共同去访问一个资源,如果有人想要去修改这个资源的时候,就需要复制一个副本,去修改这个副本,而对于其他人来说访问的资源还是原来的,不会发生变化

CopyOnWriteArrayList 源码

CopyOnWriteArrayList 的变量

CopyOnWriteArrayList 底层是也是由数组实现的。本文我们只解读添加元素和读取元素的区别,删除修改元素原理和添加元素差不多,操作时都需要进行加锁,而读操作不会加锁

// 独占锁
final transient ReentrantLock lock = new ReentrantLock();

// 存放元素的数组
private transient volatile Object[] array;

我们仔细来分析一下上面两个属性,这两个思想是 CopyOnWriteArrayList 的核心

  • lock:ReentrantLock,独占锁,多线程运行的情况下,只有一个线程会获得这个锁,只有释放锁后其他线程才能获得
  • array:存放数据的数组,关键是被 volatile 修饰了,被 volatile 修饰,就保证了可见性,也就是一个线程修改后,其他线程立即可见

CopyOnWriteArrayList 的初始化

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();
        if (c.getClass() != ArrayList.class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
    
    
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

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

CopyOnWriteArrayList 添加元素(独占锁 ReentrantLock)

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;
        // array指向新数组
        setArray(newElements);
        return true;
    } finally {
    
    
        lock.unlock(); // 解锁
    }
}

添加元素的步骤如下:

  1. 获得独占锁,将添加功能加锁
  2. 获取原来的数组,并得到其长度
  3. 创建一个长度为原来数组长度 +1 的数组,并拷贝原来的元素给新数组
  4. 追加元素到新数组末尾
  5. 指向新数组
  6. 释放锁

这个过程是线程安全的,COW 的核心思想就是每次修改的时候拷贝一个新的资源去修改,add() 方法再拷贝新资源的时候将数组容量 +1,这样虽然每次添加元素都会浪费一定的空间,但是数组的长度正好是元素的长度,也在一定程度上节省了扩容的开销

CopyOnWriteArrayList 获取元素

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];
}

读操作是天然安全的操作,而且数组本身会进行检查越界问题,因此获取元素的方法很简单,只是根据索引获取该元素

CopyOnWriteArrayListVector 小结

  • VectorCopyOnWriteArrayList 都是线程安全的 List,底层都是数组实现的
  • Vector 的每个方法都进行了加锁,而 CopyOnWriteArrayList 的读操作是不加锁的,因此 CopyOnWriteArrayList 的读性能远高于 Vector
  • Vector 每次扩容的大小都是原来数组大小的 2 倍,而 CopyOnWriteArrayList 不需要扩容,通过 COW 思想就能使数组容量满足要求
  • 两个集合都实现了 RandomAccess 接口,支持随机读取,因此更加推荐使用 for 循环进行遍历。在开发中,读操作会远远多于其他操作,因此使 CopyOnWriteArrayList 集合效率更高

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/112966164