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

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

一、异常演示



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

/**
 * list集合线程不安全问题
 */
public class ThreadDemo04 {
    public static void main(String[] args) {
        List<String> 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();
        }
    }
}



运行结果:


eccec6fe96524e5cb477c051cc901cc9.png


出现了并发修改异常

     

那么如何解决呢?


二、解决方案



1、vector

     

直接将ArrayList替换为Vector即可

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


运行结果:


e72c942069d64d9282725a604a3fab0c.png

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

     

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

8f102e8e8b38466ca80f3b03a15ec0d5.png


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


2、Collections工具类

     

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

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


运行结果:


3d7252d4779846498ceaaca6e45b9aa0.png


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

c6eb5ae7751d48a1b2a2521aeeb6b2fe.png


3、CopyOnWriteArrayList 写时复制技术


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



运行结果:

8edb7400f0ea4048ac2e674b811aa492.png


三、写时复制技术  


 

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();
        }
    }


相关文章
|
5月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
76 3
|
2月前
|
存储 安全 Java
代码审查:从 ArrayList 说线程安全
我们在编码和做代码审查的过程中,要对涉及到多线程使用的场景时刻绷着一根弦,将隐患拒之门外。
39 4
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
34 0
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
181 6
【Java学习】多线程&JUC万字超详解
|
2月前
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
46 0
|
2月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
70 0
|
4月前
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
3月前
|
监控 Java
线程池中线程异常后:销毁还是复用?技术深度剖析
在并发编程中,线程池作为一种高效利用系统资源的工具,被广泛用于处理大量并发任务。然而,当线程池中的线程在执行任务时遇到异常,如何妥善处理这些异常线程成为了一个值得深入探讨的话题。本文将围绕“线程池中线程异常后:销毁还是复用?”这一主题,分享一些实践经验和理论思考。
147 3
|
4月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
74 4