zoukankan      html  css  js  c++  java
  • JDK源码分析(10) CopyOnWriteArrayList

    概述

    CopyOnWriteArrayList是一个线程安全集合,原理简单说就是:在保证线程安全的前提下,牺牲掉写操作的效率来保证读操作的高效。所谓CopyOnWrite就是通过复制的方式来完成对数据的修改,在进行修改的时候,复制一个新数组,在新数组上面进行修改操作,这样就保证了不改变老数组,也就没有一写多读数据不一致的问题了。

    定义

    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    属性

    一个是Lock,另一个是一个对象数组。

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

    初始化

    CopyOnWriteArrayList的初始容量是0,分为这样的几个步骤:

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

    需要说明的是另一个有参构造方法,参数可以是一个集合

    /**
         * Creates a list containing the elements of the specified
         * collection, in the order they are returned by the collection's
         * iterator.
         *
         * @param c the collection of initially held elements
         * @throws NullPointerException if the specified collection is null
         */
        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);
        }
    

    方法

    add(E e)

    /**
         * 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) {
            // 锁 1.5 版本的锁 已经不用synchronizated
            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();
            }
        }
    

    从add方法中我们可以看到所谓的CopyOnWrite是如何实现的,在需要修改的时候,复制一个新数组,在新数组上修改,修改结束取代老数组,这样保证了修改操作不影响老数组的正常读取,另修改操作是加锁的,也就是说没有了线程不安全的问题。

    和ArrayList相比较,效率比较低,只添加一个元素的情况下(初始容量均为0),用时是ArrayList的5倍左右,但是随着CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代价将越来越昂贵。

    除了添加其他的修改操作也都是这样的套路,不做过多解释,如remove,也是加锁,复制新数组。

    /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).  Returns the element that was removed from the list.
         *
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        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();
            }
        }
    

    get

    public E get(int index) {
      return get(getArray(), index);
    }
    //按照下标获取数组中对应的元素
    private E get(Object[] a, int index) {
      return (E) a[index];
    }
    

    读取的方法就很简单了,按照下标获取对应的元素。

    总结

    1. 读写分离,我们修改的是新数组,读取的是老数组,不是一个对象,实现了读写分离。这种技术数据库用的非常多,在高并发下为了缓解数据库的压力,即使做了缓存也要对数据库做读写分离,读的时候使用读库,写的时候使用写库,然后读库、写库之间进行一定的同步,这样就避免同一个库上读、写的IO操作太多。
    2. 场景:读操作远多于修改操作
  • 相关阅读:
    Android(java)学习笔记98:如何让你的GridView不再滚动
    Android(java)学习笔记97:使用GridView以及重写BaseAdapter
    Android(java)学习笔记96:layout_weight使用注意事项
    Android(java)学习笔记95:Android运行时异常"Binary XML file line # : Error inflating class"
    HDU 2669 Romantic
    HDU 2674 N!Again
    二分查找
    HDU 3448 Bag Problem
    HDU 2962 Trucking
    HDU 3732 Ahui Writes Word
  • 原文地址:https://www.cnblogs.com/Tu9oh0st/p/10793507.html
Copyright © 2011-2022 走看看