CopyOnWrite容器

通俗地讲,当我们往容器中添加一个元素的时候,不是直接添加,而是对当前容器copy,复制一个容器,在这个复制的容器中添加元素,添加完之后,再将引用指向这个新容器。

优点:CopyOnWrite容器可以并发的进行读操作,而不需要加锁,因为 当前容器不会添加任何元素,所以这也是一种读写分离的思想,读和写的操作分开了。

缺点:

1.内存占用问题,产生了两个容器

2.只能保持数据的最终一致性,无法保持 实时性,所以如果希望读到新数据,不要用copyOnWrite

CopyOnWrite应用场景

用于读多写少的场景,比如白名单,黑名单,搜索中有一些关键字不可以搜索,这些关键字放在一个黑名单中,只有偶尔才更新黑名单

CopyOnWriteArrayList

来看CopyOnWriteArrayList的源码

add方法加锁,如果不加锁,就会copy出很多副本,然后把元素放在新容器,最后把引用指向了新容器

get方法没有锁,简单的从数组中获取元素。但注意,此时如果有其他线程往容器添加元素,它无法获取到新数据,只能获取到旧数据

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    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();
        }
    }

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

   @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }
}

CopyOnWriteArraySet

set的特点是去重。CopyOnWriteArraySet是用了装饰者模式,内部其实引用了一个CopyOnWriteArrayList,add方法是用了CopyOnWriteArrayList的addIfAbsent方法,如果元素不存在,才加入容器

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
}

public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

    /**
     * A version of addIfAbsent using the strong hint that given
     * recent snapshot does not contain e.
     */
    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) {
                // Optimize for lost race to another addXXX operation
                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();
        }
    }

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐