当前位置: 首页 > article >正文

[JAVAEE] 面试题(四) - 多线程下使用ArrayList涉及到的线程安全问题及解决

目录

一. 多线程下使用ArrayList

1.1. 自行判断加锁

1.2 使用Collections.synchronizedList()套壳加锁

1.3 CopyOnWriteArrayList类

二. 总结


一. 多线程下使用ArrayList

多线程下使用ArrayList会涉及到线程安全问题, 例如:

    public static void main(String[] args) throws InterruptedException{
        List<Integer> list = new ArrayList<>();
        Runnable runnable = () -> {
            for (int i = 0; i < 50000; i++) {
                list.add(i);
            }
        };

        // 创建两个线程
        for (int i = 0; i < 2; i++) {
            new Thread(runnable).start();
        }

        Thread.sleep(5000);
        System.out.println(list.size());
    }

可以发现, 最后的结果并不是期待的100000, 这是因为ArrayList中的add方法并没有处理线程安全问题.

那么, 如何解决在多线程场景下使用ArrayList产生的线程安全问题呢? 如下, 有三种解决方案.

1.1. 自行判断加锁

就是根据需要来进行加锁, 将一些可能会产生线程安全问题的操作通过加锁打包成为原子操作.

        Runnable runnable = () -> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker) {
                    list.add(i);
                }
            }
        };

结果正如我们所期待的那样.

1.2 使用Collections.synchronizedList()套壳加锁

对读操作和写操作全部都加锁, 保证线程安全. (此时返回的List对象中的重要操作都加锁了)

但是会降低程序运行效率, 因为读操作本身不会产生线程安全问题, 又加锁就是多此一举了!!!

我们来看看synchronizedList类中add方法是怎么实现的, 

        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }

此时, 结果也是正确的.

1.3 CopyOnWriteArrayList类

CopyOnWrite 写时拷贝, 实现了读与读不互斥, 读与写不互斥, 写与写互斥. 保证了线程安全.

那么, 是怎么实现读与写不互斥的呢?

在面临写操作的时候, 会依照旧数组复制一个新数组, 修改操作就在新数组上进行, 最后再将旧数组覆盖.如果在写操作的时候发生了线程切换, 并且切换到了读操作, 此时还是会读取旧数组. 保证了线程安全.

 CopyOnWriteArrayList类中add方法的实现:

        /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

结果也是正确的.


二. 总结

1. 多线程下使用ArrayList类, 涉及到了线程安全问题, 以及解决线程安全问题的方法.

2. 自行判断加锁. 效率高

3. Collections.synchronizedList()套壳封装, 效率低下. 因为对不涉及线程安全问题的操作进行加锁.(即对List接口中的所有方法进行加锁)

4. CopyOnWriteArrayList类, 写时拷贝. 实现了读与读, 读与写操作不互斥, 写与写操作互斥. 保证了线程安全, 并且效率相对高效.


http://www.kler.cn/a/385260.html

相关文章:

  • Rust闭包(能够捕获周围作用域变量的匿名函数,广泛应用于迭代、过滤和映射)闭包变量三种捕获方式:通过引用(不可变引用)、通过可变引用和通过值(取得所有权)
  • 验证二叉搜索树
  • 高防服务器和高防IP的区别是什么?
  • js例轮播图定时器版
  • 基于SSD模型的路面坑洼检测系统,支持图像、视频和摄像实时检测【pytorch框架、python源码】
  • Day 52 || 739. 每日温度 、 496.下一个更大元素 I 、503.下一个更大元素II
  • 城镇住房保障:SpringBoot系统维护与升级
  • Python基于TensorFlow实现双向循环神经网络GRU加注意力机制分类模型(BiGRU-Attention分类算法)项目实战
  • 多线程案例---阻塞队列
  • RapidrepairController
  • linux 下 signal() 函数的用法,信号类型在哪里定义的?
  • 【go从零单排】go语言中的指针
  • NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备汇聚公共资源场景方案全析
  • 如何设置 TORCH_CUDA_ARCH_LIST 环境变量以优化 PyTorch 性能
  • AutoOps 使每个 Elasticsearch 部署都更易于管理
  • C#核心(7)索引器
  • 从0开始linux(21)——文件(2)文件重定向
  • Hive 查询各类型专利 Top 10 申请人及对应的专利申请数
  • 记录offcanvas不能显示和关闭的修复方法
  • QT监控文件夹变化(文件增加、删除、改名)
  • B2C分销管理系统(源码+文档+部署+讲解)
  • C++20 STL CookBook 4:使用range在容器中创建view
  • c# 动态lambda实现二级过滤(多种参数类型)
  • 『VUE』21. 组件注册(详细图文注释)
  • Kubernetes时代的APM部署革新:基于Webhook的Agent动态注入
  • docker镜像文件导出导入