JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)

目录

 一、异常演示

二、解决方案

        1、vector 

        2、Collections工具类 

        3、CopyOnWriteArrayList 写时复制技术

三、写时复制技术     

        1 、特性

        2、原理 


 一、异常演示

        循环创建线程,将数据放入集合的同时,从集合中读取数据。

/**
 * list集合线程不安全问题
 */
public class ThreadDemo04 {
    public static void main(String[] args) {

        List list = new ArrayList<>();


        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                //向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合中获取内容
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

运行结果:

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第1张图片

        出现了并发修改异常 

        那么如何解决呢?

二、解决方案

        1、vector 

        直接将ArrayList替换为Vector即可 

//        List list = new ArrayList<>();
        List list = new Vector<>();

         运行结果:

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第2张图片

        为什么会这样呢?vector和ArrayList有什么不同呢?

        进入Vector的源码可以发现,里面几乎所有的方法都加了synchronized关键字

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第3张图片

        这样确实可以避免线程安全问题,不过效率比较低。

       2、Collections工具类 

        使用Collections工具类中的synchronizedList()方法生成线程安全的集合。 

//        List list = new ArrayList<>();
//        List list = new Vector<>();
        List list = Collections.synchronizedList(new ArrayList<>());

        运行结果:

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第4张图片

         看一下jdk1.8的API对他的介绍:

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第5张图片

        3、CopyOnWriteArrayList 写时复制技术

//        List list = new ArrayList<>();
//        List list = new Vector<>();
//        List list = Collections.synchronizedList(new ArrayList<>());
        List list = new CopyOnWriteArrayList<>();

        运行结果:

JUC学习(五):ArrayList的线程安全问题分析与解决方案(vector、Collections、写时复制技术)_第6张图片

三、写时复制技术     

        1 、特性

        它相当于线程安全的 ArrayList。和 ArrayList 一样,它是个可变数组;但是和 ArrayList 不同的时,它具有以下特性:

        1、它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
        2、 它是线程安全的。
        3、 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
        4、 迭代器支持 hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
        5、 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

        2、原理 

        写时复制技术中,读操作支持并发读,即多个线程可以同时读到集合内的元素。写操作是独立写,当一个写线程执行写操作时,所有其他写线程阻塞;当该线程写的时候,将原集合中的数据复制一份,在拷贝的集合中完成写操作后,再将该拷贝集合和原集合合并。

        为什么需要拷贝,在原数组直接修改不行吗?这篇文章讲得很清楚:CopyOnWriteArrayList写时复制的原理_Endwas的博客-CSDN博客

        最后我们来看一下源码:

    public boolean add(E e) {
        //定义可重入锁
        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();
        }
    }

你可能感兴趣的:(java,java,juc,多线程,线程安全)