• ArrayList为什么线程不安全以及三种解决办法【详细】


    不安全原因

    • 我们可以看一下ArrayList源码,找到add方法,
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从上面的代码可以看出,add()方法没有使用同步互斥,所以在多线程并发中,会出现线程异常,测试代码:

    import java.util.ArrayList;
    import java.util.UUID;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            // 创建ArrayList 集合
            ArrayList<String> list = new ArrayList<>();
    
            // 创建10个线程,往 list 中添加元素
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    // 向集合中添加内容
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    // 从集合中取出内容
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    出现异常:
    请添加图片描述

    解决办法

    Vector

    可以看一下Vector的add源码,加上了synchronized同步关键字
    但是 Vector 用的不多,因为每次对添加的元素上锁,而且使用的是重量级锁synchronized是十分占用资源的,效率是十分低下的。其用法和 ArrayList 一样。

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试代码:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    import java.util.Vector;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            // 创建ArrayList 集合
            List<String> list = new Vector<String>();
    
            // 创建10个线程,往 list 中添加元素
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    // 向集合中添加内容
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    // 从集合中取出内容
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Collections

    进入 Collections 的底层,找到 synchronizedList(List list) 方法,源代码如下,synchronizedList(List list) 方法返回指定列表支持的同步(线程安全的)列表

    public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }
    
    static <T> List<T> synchronizedList(List<T> list, Object mutex) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list, mutex) :
                new SynchronizedList<>(list, mutex));
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对 Collections 的使用如下

    List<String> list = Collections.synchronizedList(new ArrayList<>());
    
    
    • 1
    • 2

    测试代码:

    import java.util.*;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            // 创建ArrayList 集合
    //        List list = new Vector();
    
            List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
            // 创建10个线程,往 list 中添加元素
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    // 向集合中添加内容
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    // 从集合中取出内容
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    CopyOnWriteArrayList

    这是写时复制思想, 首先看add()方法中有可重入锁,这个目的是防止多个线程争抢写的权力,然后下面红框中的内容是将原件复制出来一份,然后在复印件上写,之后通过setArray()方法让原件地址指向复印件,这样可以让所有人读原件,而我只修改复印件,所以读和写不会出现冲突,因此通过加锁和写时复制思想可以很好保证了多线程情况下所有线程都可以读,但是只有一个线程在写,因此不会出现并发修改异常,如源码:

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    测试代码:

    import java.util.*;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class SetUnsefertyTest {
        public static void main(String[] args) {
            // 创建ArrayList 集合
    //        List list = new Vector();
    
    //        List list = Collections.synchronizedList(new ArrayList<>()) ;
            List<String> list =new CopyOnWriteArrayList<>() ;
            // 创建10个线程,往 list 中添加元素
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    // 向集合中添加内容
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    // 从集合中取出内容
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    三种解决方式总结

    对比三者来看,Vector和Collections虽然也可以实现同步,但由于这两种方法在底层都使用了synchronized重量级锁,使其效率很低,所以对 ArrayList 的同步主要采用 CopyOnWriteArrayList

  • 相关阅读:
    并联四足机器人项目开源教程(四) --- 使用QT创建控制上位机
    ssm基于微信小程序的学习资料销售平台+ssm+uinapp+Mysql+计算机毕业设计
    【Maven】SpringBoot多模块项目利用reversion占位符,进行版本管理.打包时版本号不能识别问题
    TXT文件恢复,简单3招,快速恢复文件!
    算法学习十八补二叉树递归套路+贪心算法一
    反序列化漏洞介绍
    PeLK:101 x 101 的超大卷积网络,同参数量下反超 ViT | CVPR 2024
    佩戴最稳固的蓝牙运动耳机、好用的运动耳机推荐
    YOLO v3源码详解
    Java“牵手”京东商品详情数据,京东商品详情API接口,京东API接口申请指南
  • 原文地址:https://blog.csdn.net/weixin_54046648/article/details/128174145