高并发容器CopyOnWriteArrayList原理解析

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发容器CopyOnWriteArrayList原理解析相关的知识,希望对你有一定的参考价值。

CopyOnWriteArrayList

实现arraylist多线程数据安全的方式

  • jdk提供的Collections.SynchronizedList() 所有方法进行添加synchronized块
 public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
  • 使用reentrantLock自己对add时,进行对读写操作加锁
List<String> list = new ArrayList<String>();
		ReentrantLock lock = new ReentrantLock();
		lock.lock();
		list.add("");
		lock.unlock();
  • 使用读写锁对读多写少的场景进行优化

	List<String> list = new ArrayList<String>();
		ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
		lock.writeLock().lock();
		list.add("");
		lock.writeLock().unlock();
		
		lock.readLock().lock();
		list.get(1);
		lock.readLock().unlock();

上面的各种加锁方式,或多或少效率上都有问题,效率上最好的读写锁,在大量写操作的时候,读操作也会被阻塞。没法进行读取数据。在业务上有时候,并不需要实时数据,需要的是缓存起来没有修改前的数据,因此引出了CopyOnWriteArrayList 这个类 可以达到在写操作时,可以读取到数据,并不阻塞起来

概念

CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器,再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。

它的优点:

  • 并发数据安全,在少量写的操作下,进行读操作,并不阻塞

缺点:​​​​​

  • 多了内存占用: 写数据是copy一份完整的数据,单独进行操作。占用双份内存。  这一点很重要,在现今多线程状态下,如果数据量相当大,但你又使用了该容器,就会导致你堆内存存到副本得数据量超级大,在不确定的情况下,有可能会导致outofmemoryerror,这就有问题了;这个问题可以使用分布式缓存来存副本,降低单机内存带来得问题。
  • 数据一致性: 数据写完之后,其他线程不一定是马上读取到最新内容。也就是事务中得幻读。

  CopyOnWriteArrayList<String> list2 = new CopyOnWriteArrayList<>();
        list2.add("1");
        list2.add("2");
        list2.add("3");
        list2.add("4");
        list2.add("5");
        list2.add("6");
        list2.get(3);
        list2.remove(1);

        int j = 0;
        for (String str : list2){
            if (j==0) list2.add("99");
            j++;
        }

源码分析

CopyOnWriteArrayList 创建基础数组的新副本,这通常成本太高,但可能效率更高,当遍历操作的数量远远超过其他方法时突变,并且在您不能或不想的情况下非常有用同步遍历,但需要排除并发线程。“快照”样式的迭代器方法使用引用迭代器运行时数组的状态创建了。

继续分析成员变量来分析

lock 锁用来在写操作时,只允许一个线程进行操作,保证数据安全性

  final transient ReentrantLock lock = new ReentrantLock();

 存储数据的数组,当然包括 transient,这个属性在集合中经常使用

 private transient volatile Object[] array;
add方法
 /**
     * 将指定的元素追加到此列表的末尾
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    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();
        }
    }

这里没有用动态扩容,原因还是没有必要而且还是会占用多的内存,本来写操作时会复制元素

remove方法

  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();
        }
    }

CopyOnWriteArraySet

S et和List重要区别:不重复

而CopyOnWriteArraySet也是基于CopyOnWriteArrayList实现的,在源代码中可以看见

private final CopyOnWriteArrayList<E> al;

从源代码中看只有一个属性就是  CopyOnWriteArrayList,至于怎么保持数据唯一性。

调用的addIfAbsent 方法,这个从名称就能看出来 如果已经存在则覆盖。

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

以及remove方法都是调用的CopyOnWriteArrayList中的方法进行实现,本身只是对这个对象进行持有。

 public boolean remove(Object o) {
        return al.remove(o);
    }

总结

整个CopyOnWriteArrayList解决就是在写操作时,无法读取的问题,用于读多写少的场景,因为副本的关系,因此在工作中尽量谨慎使用。也可以自己写一个 读写list来保证数据的安全。

以上是关于高并发容器CopyOnWriteArrayList原理解析的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程:并发容器之CopyOnWriteArrayList(转载)

CopyOnWriteArrayList并发容器

Java并发编程:并发容器之CopyOnWriteArrayList

CopyOnWriteArrayList并发容器源码解析

CopyOnWriteArrayList并发容器源码解析

并发容器之CopyOnWriteArrayList