【并发容器精讲二、】CopyOnWriteArrayList

    技术2022-07-10  110

    文章目录

    1. 诞生的历史和原因2. 适用场景3. 读写规则4. 实现原理&源码分析5. 缺点

    1. 诞生的历史和原因

    代替Vector和SyschronizedList 就像 ConcurrentHashMap 代替了SyschronizedMap的原因一样Vector和SyschronizedList 锁的粒度太大,并发效率较低,并且迭代时无法编辑Copy-On-Write 并发容器还包括CopyOnWriteArraySet,用来代替同步 Set

    2. 适用场景

    读操作尽可能的快一些,而写即使慢一些也没关系

    3. 读写规则

    回顾读写锁:读读共享、其他都互斥,(写写互斥,读写互斥,写读互斥)读写锁规则的升级:读取是完全不用加锁的,并且更厉害的是写入也不会阻塞读取操作,只有写入写入才会同步等待

    ArrayList

    public static void main(String[] args) { List list=new ArrayList(); list.add("11"); list.add("22"); list.add("33"); Iterator iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(list); Object next = iterator.next(); System.out.println(next); if(next.equals("11")){ list.remove("22"); } if(next.equals("33")){ list.add("2222"); } } }

    CopyOnWriteArrayList

    public static void main(String[] args) { CopyOnWriteArrayList list = new CopyOnWriteArrayList(); list.add("11"); list.add("22"); list.add("33"); list.add("44"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(list); Object next = iterator.next(); System.out.println(next); if (next.equals("11")) { list.remove("22"); } if (next.equals("33")) { list.add("2222"); } } }

    通过案例我们可以发现 ArrayList 迭代修改的时候会 抛出异常 ,而CopyOnWriteArrayList 不会

    4. 实现原理&源码分析

    CopyOnWrite的含义

    创建新副本、读写分离

    不可变原理

    迭代的时候

    我们看下ArrayList 源码为什么会报错

    @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }

    点开方法,他会进行一个比较操作所以会出现异常

    final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

    我们看下 CopyOnWriteArrayList 源码

    这是CopyOnWriteArrayList存放数据的地方,只能通过getArray获取 并且他会上锁,用的ReentrantLock

    /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;

    初始化 :构造函数 新建个空的数组

    /** * Sets the array. */ final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); }

    add 方法

    /** * Appends the specified element to the end of this list. * * @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(); //获取长度 //copy出一个新的数组 Object[] newElements = Arrays.copyOf(elements, len + 1); //将我们的参数添加到这个位置 newElements[len] = e; //set方法添加至 setArray(newElements); return true; } finally { //释放锁 lock.unlock(); } }

    get方法

    @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); } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } @SuppressWarnings("unchecked") public E next() { //判断 true 和 false //逻辑简单了许多 if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; }

    5. 缺点

    数据一致性问题 :CopyOnWrite只能保证数据最终一致性的问题,不能保证数据实时一致性,所以希望写入的数据马上能读到,不要使用CopyOnWrite内存占用问题:因为CopyOnWrite的写是复制机制,所以在写的操作,所以内存中会存在2个对象

    个人博客地址:http://blog.yanxiaolong.cn/

    Processed: 0.009, SQL: 9