CopyOnWriteArrayList详解

CopyOnWriteArrayList详解

简介

Copy-On-Write(COW):
开始时共享同一资源,但一个线程修改部分内容时,才会用修改后的内容覆盖旧的内容.是一种延时懒惰策略.

有两种COW机制的并发容器:

  • CopyOnWriteArrayList.
  • CopyOnWriteArraySet.

原理

  • 是一种写时复制容器.
  • 当向容器添加元素时,不直接添加元素,而是先将当前容器复制一份,向复制得到的新容器添加元素,再将原容器的引用指向新的容器.
  • 并发读时不需要加锁,并发写时加锁.
  • 读写分离,读和写为不同的容器.

实现

添加元素

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(); // 释放锁
    }
}

步骤:

  1. 加锁
  2. 复制原来的元素到新容器中
  3. 向新容器中添加元素
  4. 更新引用指向新容器

获取元素

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

注: 获取元素无需加锁.

更新元素

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(); // 释放锁
    }
}

步骤:

  1. 获取旧的容器.
  2. 若指定位置的元素与设定的新值不同,则构造新容器,并更新该位置上的值.
  3. 若设定的新值与旧值相同,则无需更新.

删除元素

类似与添加元素.

  1. 加锁
  2. 构造新容器
  3. 更新引用指向新容器
  4. 释放锁

添加不存在的元素

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 加锁
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1); // 复制旧容器中的元素
        newElements[len] = e; // 向新容器中新增元素
        setArray(newElements); // 更新引用
        return true;
    } finally {
        lock.unlock(); // 解锁
    }
}

流程:

  1. 加锁
  2. 检查新增的元素是否已存在,若已存在,则无需新增
  3. 若不存在,则构造新容器,将旧值复制到新容器中,并添加新值
  4. 释放锁

应用

  • 用于读多写少的并发场合.(白名单,黑名单,商品类目的访问等)
  • 注意:
    • 减少扩容,初始化大小,减少扩容的开销.
    • 使用批量添加.每次添加都会进行复制,减少复制的次数.
  • 问题:
    • 内存占用高.进行写操作时,内存中同时存在旧数据和新数据,可能导致频繁的Young GC和Full GC.可通过压缩容器中的元素大小实现.
    • 数据一致性.CopyOnWriteArrayList只保证数据的最终一致性,不保证数据的实时一致性.若希望写入的数据实时读到,不可使用CopyOnWrite容器.

参考:

原文地址:https://www.cnblogs.com/truestoriesavici01/p/13216170.html