java集合之CopyOnWriteArrayList

现在项目面临的并发场景越来越多。而像vector,hashTable这一类集合虽然保证的线程的安全行。但是无法兼顾性能。所以出现了如ConcurrentHashMap这一类兼顾性能与线程安全的集合。而对于List和Set同样也有新的线程安全集合——CopyOnWriteArrayList。
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
CopyOnWriteArrayList的构造方法和ArrayList是基本相同的,主要看其添加,删除方法来研究其如何保证性能以及线程安全。

	transient final ReentrantLock lock = new ReentrantLock();
	//添加方法
	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();
        }
    }
    //移除方法
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    //获取方法
    private volatile transient Object[] array;
    final Object[] getArray() {
        return array;
    }
    public E get(int index) {
        return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

可以从代码看出集合类中引入了重入锁ReentrantLock来进行线程安全控制。在添加以及删除操作中。加入同步锁。将原来的数组复制一份出来。在复制的集合中进行修改。而不影响其他线程对原来数组的获取。这样保证了集合的最终一致性。也兼顾了性能。为什么是最终一致性呢。因为他说复制一份数组出来。如果同时有2个线程操作数组。一个在修改一个在获取。那只能获取到修改之前的数组。无法获取到正在修改的数组。所以只能保证最终的一致性,无法保证实时的一致。
适用于用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景
同样基于这种读写思想分离来保障线程安全的还有CopyOnWriteArraySet。
CopyOnWriteArraySet是实现了set的集合。我们都知道set的实现类基本都是依靠map来进行存储的。但是CopyOnWriteArraySet是不一样的。他是依靠CopyOnWriteArrayList来进行实现的也就是数组进行存储。
我们看其构造方法

	private final CopyOnWriteArrayList<E> al;
	public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    public CopyOnWriteArraySet(Collection<? extends E> c) {
        al = new CopyOnWriteArrayList<E>();
        al.addAllAbsent(c);
    }
    //引用了CopyOnWriteArrayList进行数据存储

再看其添加和移除的方法

	public boolean add(E e) {
        return al.addIfAbsent(e);
    }
    public boolean addIfAbsent(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Copy while checking if already present.
            // This wins in the most common case where it is not present
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = new Object[len + 1];
            for (int i = 0; i < len; ++i) {
                if (eq(e, elements[i]))
                    return false; // exit, throwing away copy
                else
                    newElements[i] = elements[i];
            }
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    public boolean remove(Object o) {
        return al.remove(o);
    }

这里的添加方法和CopyOnWriteArrayList不一样。他是新建了一个比原来大的数组。然后循环原数组将元素添加进去。在循环的过程中判断添加的对象是否和原来的对象相同。如果相同则添加失败。由此来保证数组对象无重复。缺点也和CopyOnWriteArrayList一样。无法保证数据的实时一致。只能保证最终一致性。同时如果集合内存过大。也会占用过多的内存空间。导致出发GC影响性能。

你可能感兴趣的:(自学整理)