JAVA多线程基础 之十CopyOnWrite容器

    xiaoxiao2022-07-02  100

    定义:

    CopyOnWrite机制称为写时复制,理解起来很简单,就是执行修改操作时进行底层数组复制,使得修改操作在新的数组上进行,不妨碍原数组的并发读操作,复制修改完成后更新原数组引用变量。

    原理

    每个修改之前都加上ReentrantLock使并发写操作互斥执行,避免多次数据复制修改。所以能够实现读写分离,但是写写同步执行。读写并发环境中,在将原数组对象引用变量更新为复制修改完成后的数组对象之前,读的数组对象和写的数组对象可能不一致,所以不能保证实时数据一致性,只能保证最终一致性。

    实现

    在concurrent包下CopyOnWrite机制的实现有两种,CopyOnWriteArrayList和CopyOnWriteArraySet。其实CopyOnWriteArraySet就是一个CopyOnWriteArrayList,不过就是在方法中避免重复数据而已,甚至这些避免重复数据的函数也是在CopyOnWriteArrayList中定义的,CopyOnWriteArraySet中只是包含一个CopyOnWriteArrayList的属性,然后在方法上做个包装,除了equals方法外,其他当前类中的所有函数都是调用的CopyOnWriteArrayList的方法,所以严格来讲可以使用一个CopyOnWriteArrayList作为具有Set特性的写时复制数组(不过就是没有继承AbstractSet)。

    CopyOnWriteArrayList中的属性

    //序列化序列号,不定义的话,会自动生成

    private static final long serialVersionUID = 8673264195747942595L;

    transient final ReentrantLock lock = new ReentrantLock();

    private volatile transient Object[] array;

    //看到这里的数组以transient修饰,就应该知道后面一定有自定义的writeObject和readObject方法

     

    因为主要操作就是lock加锁,读写、复制数组,所以重点照看就是lock、array这两个对象。

    后面常见的array操作:

    final Object[] getArray() {

            return array;

        }

    final void setArray(Object[] a) {

            array = a;

    }

     

    在array数组上的操作就这样两种类型:

    1.不加锁的size(),isEmpty(),indexOf(),contains(),get()。称为读操作,非修改操作

    2.加锁的set(),add(),remove()。称为修改操作

    最简单的无检查读操作(当然这个无检查是程序无检查,JVM自然是需要检查的,毕竟java是安全语言):

     

    public E get(int index) {

            return get(getArray(), index);

        }

    private E get(Object[] a, int index) {

            return (E) a[index];

    }

     

    以set操作为例:

    public E set(int index, E element) {

            final ReentrantLock lock = this.lock;

            lock.lock();//加锁执行

            try {

                Object[] elements = getArray();

                E oldValue = get(elements, index);//array中即当前数组的对应位置元素oldValue

     

                if (oldValue != element) {//元素需要替换时

                    int len = elements.length;

                    Object[] newElements = Arrays.copyOf(elements, len);//array的分身,新数组对象

                    newElements[index] = element;

                    setArray(newElements);//更新操作  array=newElements

                } else {//不需要替换元素,其实不太明白下面那句话的意思

                                //并不是完全的空操作;保证volatile写操作的语义

                    // Not quite a no-op; ensures volatile write semantics

                    setArray(elements);

                }

                         /*关于array为什么使用volatile修饰的个人理解:

                         我们知道在jdk5后,将volatile修饰单例对象作为实现DCL的一种方式,使用的就是volatile的

                         编译后指令形成内存屏障,禁止指令重排序的特点,如下

                         private static volatile Instance instance=null;//单例对象

                         public static Instance getInstance(){

                                if(instance==null){

                                       synchronized(DoubleCheckedLocking.class){

                                              if(instance==null){

                                                     instance=new Instance();//在当前步骤,使得Instance实例构造在instance变量赋值

                                                     //之前完成,禁止虚拟机或处理器的重排序,从而保证instance!=null时,对象已经构造完成

                                              }

                                       }

                                }

                         }

                         因为读操作是不需要加锁执行的,可以实时访问array对象,所以给array加volatile修饰是基于上面相同的思考

                         保证array的修改完成在对array变量的更新之前

                         */

                return oldValue;

            } finally {//为了忘记释放锁或者中间抛出什么异常,尽量把释放锁操作放在finally中

                lock.unlock();

            }

        }

    总结

    Copy-On-Write简称COW,是一种用于程序设计中的优化策略。

    JDK里的COW容器有两种:CopyOnWriteArrayList和CopyOnWriteArraySet,COW容器非常有用,可以在非常多的并发场景中使用到。

    什么是Copy-On-Write容器?

    CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁。因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

    底层方法add()、remove()等底层会加锁。

    CopyOnWrite机制保证读写分离,在读多写少的并发环境下可以起到很好的作用,但是因为是写时复制,所以不能保证数据的实时性,而只能保证最终一致性。

    最新回复(0)