zoukankan      html  css  js  c++  java
  • CopyOnWriteArrayList类

    CopyOnWriteArrayList是一个线程安全的ArrayList,对其修改操作都是在底层的一个复制的数组上进行的,也就是使用了写时复制策略。

     一、变量与构造方法

        /** The lock protecting all mutators */
        final transient ReentrantLock lock = new ReentrantLock();//独占锁,保证同一时刻只有一个线程对array修改
    
        /** The array, accessed only via getArray/setArray. */
        private transient volatile Object[] array;//存放对象的数组
    
        public CopyOnWriteArrayList() {//无参实例化 array = new Object[0];
            setArray(new Object[0]);
        }
    
        /**
         * Collection接口实例创建实例化 将Collection的array复制给CopyOnWriteArrayList的array*/
        public CopyOnWriteArrayList(Collection<? extends E> c) {
            Object[] elements;
            if (c.getClass() == CopyOnWriteArrayList.class)
                elements = ((CopyOnWriteArrayList<?>)c).getArray();
            else {
                elements = c.toArray();
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elements.getClass() != Object[].class)
                    elements = Arrays.copyOf(elements, elements.length, Object[].class);
            }
            setArray(elements);
        }
    
        /**
         * 直接数组实例化 复制给array*/
        public CopyOnWriteArrayList(E[] toCopyIn) {
            setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
        }

    二、写时复制策略:增删改时复制数组,修改后替换原数组。

    1.boolean add(E e)方法

        public boolean add(E e) {
            //获取独占锁
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                //复制array到新数组,添加元素到新数组
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                //新数组替换原数组
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

    2.E remove(int index)

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

    3.E set(int index, E element):

        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 {
                    //修改元素与原元素相同
                    // Not quite a no-op; ensures volatile write semantics 不是完全没有操作,需要确保volatile的写语义
                    setArray(elements);
                }
                return oldValue;
            } finally {
                lock.unlock();
            }
        }

    三、弱一致性问题,读操作时不能满足实时性要求

    弱一致性问题是由写时复制策略产生的,读写操作并发时,读操作时数组的版本可能是写操作版本的原数组,不是替换的新数组。不能保证实时性要求。

    1.E get(int index):未加锁,弱一致性问题:remove()、set()的同时执行get()方法,可能会导致获取已删除、修改前的元素。

        public E get(int index) {
            return get(getArray(), index);
        }
    
        private E get(Object[] a, int index) {
            return (E) a[index];
        }

    2.迭代器iterator的弱一致性fast-safe机制,返回迭代器后,遍历的是返回迭代器时数组的一个快照。其他线程对list的增删改对迭代器是不可见的。

    快照版本,指针指向的是增删改前的数组,而不是增删改后的新数组,操作的是两个不同的数组

    为什么写操作加锁了已经能保证线程安全了,还要复制数组?这里可能就是答案吧,读操作有get和iterator,复制数组是为了实现fast-safe机制,避免fast-fail。

        public Iterator<E> iterator() {
            return new COWIterator<E>(getArray(), 0);
        }
    
        static final class COWIterator<E> implements ListIterator<E> {
            /** Snapshot of the array */
            private final Object[] snapshot;
            /** Index of element to be returned by subsequent call to next.  */
            private int cursor;
    
            private COWIterator(Object[] elements, int initialCursor) {
                cursor = initialCursor;
                snapshot = elements;//快照版本,指针指向的是增删改前的数组,而不是增删改后的新数组,操作的是两个不同的数组
            }
         ...
        }

    例子:

    public class COWTest {
    
        private static volatile CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
    
        public static void main(String[] args) throws InterruptedException {
            cowList.add("hello");
            cowList.add("world");
            cowList.add("skip1");
            cowList.add("skip2");
            Thread threadOne = new Thread(new Runnable() {
                @Override
                public void run() {
                    cowList.set(0,"hello ");
                    cowList.remove(3);
                    cowList.remove(2);
                }
            });
    
            Iterator<String> iterator = cowList.iterator();
    
            threadOne.start();
            threadOne.join();
    
            while (iterator.hasNext()){
                System.out.println(iterator.next());//hello
    world
    skip1
    skip2
    
            }
    
            System.out.println(cowList);//[hello , world]
        }
    }

    总结:

    1.写时复制策略,写时加锁了已经可以保证线程安全了,复制是为了避免fast-fail机制,实现fast-safe,每次增删改都会复制生成一个新数组,当数组太大时,每次写操作代价太大,性能很低,另外JVM规定大对象会直接进入老年代,可能导致full gc

    2.弱一致性,虽然能做到最终一致性,但是不能保证实时性需求

    3.将读写分开处理,适合读多写少的场景。

    4.扩容与ArrayList不同,add时+1,remove时-1

    4.增删改共用同一个独占锁,所以操作互斥。

    参考自《java并发编程之美》

  • 相关阅读:
    linux 调试利器gdb, strace, pstack, pstree, lsof
    使用Istio治理微服务入门
    JAVA多线程及线程状态转换
    Oracle
    RMA Sales Order – Stuck with “Awaiting Return Disposition”
    XP中IIS“HTTP 500
    Oracle ERP View
    WIP and COST Frequently Used Troubleshooting Scripts (Doc ID 105647.1)
    SQL -- What Tables Queries are Used to Display the Counts in the Inventory Account Periods form (INVTTGPM.fmb) (Doc ID ID 357997.1)
    oracle ebs 11i > concurrent programs –> request group –> responsibility
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12159635.html
Copyright © 2011-2022 走看看