zl程序教程

您现在的位置是:首页 >  后端

当前栏目

大数据必学Java基础(六十三):COW并发容器讲解

JAVA基础并发容器数据 讲解 必学 Cow
2023-06-13 09:13:40 时间

​COW并发容器讲解

COW类并发容器,全称:Copy On Write容器,写时复制容器。(读写分离容器)

原理:

向容器中添加元素时,先将容器进行Copy复制出一个新容器,然后将元素添加到新容器中,再将原容器的引用指向新容器。

并发读的时候不需要锁定容器,因为原容器没有变化,所以可以读取原容器中的值,使用的是一种读写分离的思想。

这种设计的好处是什么呢?

注意上面的操作arr数组本身是无锁的,没有锁,在添加数据的时候,做了额外的复制,

此时如果有线程来读数据,那么读取的是老arr的数据,此时arr的地址还没有改呢,在我添加元素的过程中,

无论有多少个线程来读数据,都是读的原来的arr,不是新的arr

所以性能很高,读写分离。提高了并发的性能。如果再读再复制...

注意:

CopyOnWrite容器只能保证数据的最终一致性,不能保证数据实时一致性。

所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

适合特定场合:

这个应用场景显而易见,适合读多写少的情况。如果一万个线程都添加操作,都在集合中添加数据,那数组不断复制,长度不断+1,

那JVM肯定一直往上飙升,你用的时候肯定要评估使用场景的。

由于每次更新都会复制新容器,所以如果数据量较大并且更新操作频繁则对内存消耗很高,建议在高并发读的场景下使用。

主要讲解:

COW容器有两种一种是CopyonWriteArrayList,一种是CopyOnWriteArraySet

一个是替代ArrayList,一个是代替Set

一、CopyOnWriteArrayList

1、代码

package com.lanson.test04;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author : lansonli
 */
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        //添加方法:
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        System.out.println(list);//[1, 2, 3, 4]
        list.add(3);//add方法无论元素是否存在,都可以添加进去--》添加重复的元素
        System.out.println(list);//[1, 2, 3, 4, 3]
        //adj. 缺席的;缺少的;心不在焉的;茫然的
        list.addIfAbsent(33);//添加不存在的元素--》不可以添加重复的数据
        System.out.println(list);//[1, 2, 3, 4, 3, 33]
    }
}

2、源码分析

public class CopyOnWriteArrayList<E>{
        //底层基于数组实现的
        private transient volatile Object[] array;

        public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

        final void setArray(Object[] a) {
        array = a; // array = new Object[0]
    }
        //add方法:
        public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
                        //返回底层array数组,给了elements
            Object[] elements = getArray();
                        //获取elements的长度---》获取老数组的长度
            int len = elements.length;
                        //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
            Object[] newElements = Arrays.copyOf(elements, len + 1);
                        //将e元素放入新数组最后位置
            newElements[len] = e;
                        //array数组的指向从老数组变为新数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


        final Object[] getArray() {
        return array;//返回底层数组
    }


        private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
                        //取出array数组给current
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                                //遍历老数组:
                for (int i = 0; i < common; i++)
                                        //eq(e, current[i])将放入的元素和老数组的每一个元素进行比较,如果有重复的元素,就返回false,不添加了
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
                        //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
            Object[] newElements = Arrays.copyOf(current, len + 1);
                        //将e元素放入新数组最后位置
            newElements[len] = e;
                        //array数组的指向从老数组变为新数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


}

二、CopyOnWriteArraySet

1、代码

/**
 * @author : lansonli
 */
public class Test02 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建一个集合:
        CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
        //在这里也体现出Set和List的本质区别,就在于是否重复
        //所以add方法直接不可以添加重复数据进去
        set.add(1);
        set.add(2);
        set.add(2);
        set.add(7);
        System.out.println(set);//[1, 2, 7]

    }
}

2、源码

public class CopyOnWriteArraySet<E>{
        //CopyOnWriteArraySet底层基于CopyOnWriteArrayList
        private final CopyOnWriteArrayList<E> al;
        
        public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
        
        //添加方法:
        public boolean add(E e) {
        return al.addIfAbsent(e);//底层调用的还是CopyOnWriteArrayList的addIfAbsent
    }
}

三、总结

由上面的源码看出,每次调用CopyOnWriteArraySet的add方法时候,其实底层是基于CopyOnWriteArrayList的addIfAbsent,

每次在addIfAbsent方法中每次都要对数组进行遍历,所以CopyOnWriteArraySet的性能低于CopyOnWriteArrayList