浅析Java并发编程:如何做到线程安全-Collections.synchronizedList和CopyOnWriteArrayList的2种实现

  在多线程环境下ArrayList是线程不安全的,所以需要使用线程安全的List,我第一时间使用的是Collections.synchronizedList(new Arraylist<>()),但是在实际使用过程中却发生了安全事件。

一、使用Collections.synchronizedList(new Arraylist<>()) + 同步解决线程安全问题

  当前存在一个类IntegerList,该类对象在多线程环境中添加元素和获取获取

复制代码
public class IntegerList {

    final private List<Integer> integerList;

    public IntegerList(List<Integer> integerList) {
        this.integerList = integerList;
    }

    /*
    * 获取列表中小于integer的所有数
    * */
    public List<Integer> getLessThanInteger(Integer integer){
        List<Integer> result=new ArrayList<>();
        for (Integer item : integerList) {
            if(item<=integer){
                result.add(item);
            }
        }
        return result;
    }

    private void addInteger(Integer integer){
        integerList.add(integer);
    }

    @Override
    public String toString() {
        return "IntegerList{" +
                "integerList=" + integerList +
                '}';
    }
}    
复制代码

  使用ArrayList 线程不安全会报错ConcurrentModificationException

复制代码
 public static void main(String[] args) {
        IntegerList integerList = new IntegerList(new ArrayList<>());
        for (int i = 0; i < 100; i++) {
            final int i1=i;
            new Thread(()->{
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }
复制代码

  这个是单线程也会报错的,原因是:添加的元素的同时,又获取元素,所以不安全。

  报错原因是在getLessThanInteger中使用增强for循环遍历了列表,集合的增强for循环是使用Iterator实现的,遍历过程中如果发现其他线程有修改列表就会抛异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dPYg9cl-1618477707391)(47C1CB1F99614FA1AA6696CA44A418A1)]

  编译后Class文件

  Iterator next()进行修改检查,如果集合被修改过,就会抛异常

  使用线程安全的Collections.synchronizedList 依然会报错

复制代码
public static void main(String[] args) {
        IntegerList integerList = new IntegerList( Collections.synchronizedList(new ArrayList<>()));
        for (int i = 0; i < 100; i++) {
            final int i1=i;
            new Thread(()->{
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }
复制代码

  为什么线程安全的Collections.synchronizedList在多线程下还是会报错?

  原因在于返回的迭代器是线程不安全的,在jdk源码里面注释说明,要用户自己使用同步来控制遍历迭代器

  使用同步改造:加入同步代码块,并且锁定的对象是 integerList

  这样就可以成功执行了。

二、使用CopyOnWriteArrayList 完美解决迭代过程不安全问题

复制代码
public static void main(String[] args) {
        IntegerList integerList = new IntegerList(new CopyOnWriteArrayList<>());
        for (int i = 0; i < 100; i++) {
            final int i1 = i;
            new Thread(() -> {
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }
复制代码

  CopyOnWriteArrayList 解决线程安全的原因。CopyOnWriteArrayList 获取元素的时候使用的是当前数组Array的一个快照,而修改元素的时候,会复制一份Array再进行修改,修改不会对原本的Array产生影响,修改完后会覆盖原本的Array

  修改元素:

  迭代器:

  我们在使用线程安全的List应该使用CopyOnWriteArrayList,防止在遍历迭代器过程中出现异常,缺点是CopyOnWriteArrayList会使用额外的内存空间。

原文链接:https://blog.csdn.net/piexie8364/article/details/115731723

posted @   古兰精  阅读(636)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2019-09-09 浅析Hooks的产生背景及在vue里使用hooks的意义
点击右上角即可分享
微信分享提示