记一次关于ArrayList 非线程安全导致的生产问题


        ForkJoinPool forkJoinPool = new ForkJoinPool(5);

        //需要下单并成功创建的计划
        List<String> sourceList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            sourceList.add("S-erqe");
            sourceList.add("S-43432");
            sourceList.add("S-5234");
        }
        for (int i = 0; i < 100; i++) {
            sourceList.add("H-353452");
            sourceList.add("H-353452");
            sourceList.add("H-353452");
        }
        //需要下单并成功创建的计划
        List<String> startWithSList = new ArrayList<>();
        //没有需要下订单的计划
        List<String> startWithHList = new ArrayList<>();

        //没有需要下订单的计划
        List<String> result = new CopyOnWriteArrayList<>();
        result = forkJoinPool.submit(() -> sourceList.parallelStream().map(e -> {
            if (e.startsWith("S")) {
                startWithSList.add(e);
            }
            if (e.startsWith("H")) {
                startWithHList.add(e);
            }
            return e.split("-")[1];
        }).collect(Collectors.toList())).join();
        System.out.println("以S开头的集合有:");
        startWithSList.forEach(s -> {
                    if (s == null) {
                        System.out.println(s);
                    }
                }
        );
        System.out.println("以H开头的集合有:");
        startWithHList.forEach(s -> {
                    if (s == null) {
                        System.out.println(s);
                    }
                }
        );
        System.out.println("去除开头的所有集合:");
        result.forEach(s -> {
                    System.out.print(s+",");
                }
        );
    }

             运行结果一:在多线程中使用 ArrayList 来分别收集以S,和H 开头的 字符串。 看打印的结果发现出现 null ;

以S开头的集合有:
null
null
以H开头的集合有:
null
去除开头的所有集合:
erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,erqe,43432,5234,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452,353452

            运行结果二: 出现了数组越界问题;

         原因分析: ArrayList 是属于线程不安全的类;在多线程运行过程中出现了对应的线程安全问题。

         解决方式:使用线程安全的对应的类,如:CopyOnWriteArrayList 代替ArrayList.  

          或者是  Collections.synchronizedList(startWithHList)

 

           至此,就可以看到不会再出现上面的空指针和数组越界的问题了。

           ArrayList 为什么会是线程不安全的呢?

           首先查看一下,ArrayList 的add方法实现:我们知道 size++ 是非原子性操作的。

           为什么会出现null 的情况呢?

          当线程A 和线程B 同时执行 size++ 的操作时,假设 size = 3;A和B 都是 elementData [3++] = e;  此时 element[3] 可能为A线程的e,也可能时B线程的e。而此时 e 可能被加了2次 e = 5 ;这是 element[4] 就没有被赋值,此时就出现了 null的情况。     
         

           从新认识一下ArrayList 的数据结构:

         1,底层的数据存储是放在一个数组里的[]. 这里涉及到一个数组长度的初始化大小问题: 默认大小为10; 通常使用的默认无参构造方法,实现即为 this.elementData ={};此时的size = 0;

         2,新增方法;

                2.1 先对初始化的数组进行扩容。如果是首次添加数据,则默认初始化为 10;此时会调用一次扩容算法。新的数组长度会是原长度的1.5倍;确定好数组需要扩容的大小后,开始复制数组到新数组中;

                 中间插入一个小疑问:  

              2.2 初始化/扩容好对应的数组大小后,就可以开始添加新的数据了。

           到这里就知道为什么会出现数组越界的情况了。

           当新增的时候,会首先确认是否需要扩容。此时A.B 线程同时执行了add()方法,都已经确定了此时的数组的容量大小。因为是线程共享的size .此时 size++ 可能会大于 初始化好的数组容量导致 在执行elementData[size++]的时候 已经超过了数组大小,此时就会报数组越界异常;

         再看看线程安全的类是如何解决这个问题的呢?

         CopyOnWriteArrayList加锁的方式,保证线程安全。

   Collections.synchronizedList(startWithHList) 在线程不安全的ArrayList .add()上加了一把锁。从而保证了线程安全。

         结束语:虽然都知道ArrayList ,HashMap 是线程不安全的类,但是写代码的时候还是哐哐就给搞上去了。要不是这次生产出现了一个偶发的bug ,也不会有如此深刻的印象,还是要学以致用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值