zoukankan      html  css  js  c++  java
  • 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容器.

    参考:

  • 相关阅读:
    自闭的D7
    D2
    Codeforces Round #531 (Div. 3)
    hello 2019 D
    牛客练习赛36B
    cf954H
    gym102007 E
    Gym 101972
    Gym 101810
    试题 历届试题 青蛙跳杯子(bfs)
  • 原文地址:https://www.cnblogs.com/truestoriesavici01/p/13216170.html
Copyright © 2011-2022 走看看