COW机制以及相关类

  • Vector和SynchronizedList

    • 咱们知道ArrayList是用于替代Vector的,Vector是线程安全的容器。由于它几乎在每一个方法声明处都加了synchronized关键字来使容器安全。
    • 若是使用Collections.synchronizedList(new ArrayList())来使ArrayList变成是线程安全的话,也是几乎都是每一个方法都加上synchronized关键字的,只不过它不是加在方法的声明处,而是方法的内部
    • 多线程下for循环迭代Vector或者SynchronizedList,进行delete和get操做会发生数组下标错误的异常。java

      • 在JDK5之后,Java推荐使用for-each(迭代器)来遍历咱们的集合,好处就是简洁、数组索引的边界值只计算一次
    • 若是使用for-each(迭代器)来作上面的操做,会抛出ConcurrentModificationException异常。
    • 若是想要完美解决上面所讲的问题,咱们能够在遍历前加锁数组

      • 遍历一下容器都要我加上锁,这这这不是要慢死了吗。的确是挺慢的。由于加锁粒度太大。
  • CopyOnWriteArrayList是同步List的替代品,CopyOnWriteArraySet是同步Set的替代品。

    • Hashtable、Vector加锁的粒度大(直接在方法声明处使用synchronized)
    • ConcurrentHashMap、CopyOnWriteArrayList加锁粒度小(用各类的方式来实现线程安全,好比咱们知道的ConcurrentHashMap用了cas锁、volatile等方式来实现线程安全..)
    • JUC下的线程安全容器在遍历的时候不会抛出ConcurrentModificationException异常
    • 因此通常来讲,咱们都会使用JUC包下给咱们提供的线程安全容器,而不是使用老一代的线程安全容器。
  • CopyOnWriteArrayList实现原理

    • CopyOnWriteArrayList是线程安全容器(相对于ArrayList),底层经过复制数组的方式来实现。
    • CopyOnWriteArrayList在遍历的使用不会抛出ConcurrentModificationException异常,而且遍历的时候就不用额外加锁
    • 元素能够为null
    /** 可重入锁对象 */
        final transient ReentrantLock lock = new ReentrantLock();
        /** CopyOnWriteArrayList底层由数组实现,volatile修饰 */
        private transient volatile Object[] array;
    
        final Object[] getArray() {
            return array;
        }
        final void setArray(Object[] a) {
            array = a;
        }
        // 初始化CopyOnWriteArrayList至关于初始化数组
        public CopyOnWriteArrayList() {
            setArray(new Object[0]);
        }
    • CopyOnWriteArrayList底层就是数组,加锁就交由ReentrantLock来完成。安全

      • 经过代码咱们能够知道:在add(),set(),remove() 的时候就上锁,并复制一个新数组,增长操做在新数组上完成,将array指向到新数组中,最后解锁。
      • 在修改时,复制出一个新数组,修改的操做在新数组中完成,最后将新数组交由array变量指向
      • 写加锁,读不加锁
  • CopyOnWriteArrayList缺点

    • 内存占用:若是CopyOnWriteArrayList常常要增删改里面的数据,常常要执行add()、set()、remove()的话,那是比较耗费内存的。多线程

      • 由于咱们知道每次add()、set()、remove()这些增删改操做都要复制一个数组出来。
    • 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性线程

      • 从上面的例子也能够看出来,好比线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。可是线程A迭代出来的是原有的数据。