JavaArrayList线程不安全的坑

Posted maerpao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaArrayList线程不安全的坑相关的知识,希望对你有一定的参考价值。

问题复现:

使用Java的steam().paralleStream(),foreach()方法向ArrayList添加数据,导致ArrayList中出现空值,代码如下:

    public static void main(String[] args) 
        List<Integer> a = new ArrayList<>();
        for (int i = 0; i < 20; i++) 
            a.add(i);
        
        List<String> a1 = new ArrayList<>();
        a.parallelStream().forEach(x -> 
            a1.add(String.valueOf(x));
        );
        System.out.println(a1);
    

输出结果:

[12, 6, 14, 5, 13, 8, 11, 9, 10, 7, 2, 17, 1, 4, 0, null, 19, 18, 16, 15]

如上,会出现null值

问题分析:

ArrayList通过size变量来控制当前数组元素的指针,代码:

    private void add(E e, Object[] elementData, int s) 
        if (s == elementData.length) 
            elementData = this.grow();
        

        elementData[s] = e;
        this.size = s + 1;
    

    public boolean add(E e) 
        ++this.modCount;
        this.add(e, this.elementData, this.size);
        return true;
    

如果此时出现多线程操作,例如Thread1获取到size=3,对数组的第三个元素进行赋值,此时Thread2获取到的size也等于3,也对数组第三个元素进行赋值,此时Thread1和Thread2都操作的第三个元素,然后此时Thread1进行size+1操作,Thread2进行加size+1操作,当前size值为5,但是实际数组中只有4个元素,有一个为null,因为Thread2本来应该对数组第4个元素赋值,因为线程不安全导致拿到的size值为3

解决方式:

1.使用Vector,所有方法用synchronized修饰,数组结构

2.Collections.sychronizedList(list)的方式获取一个list的代理,不限于ArrayList,可以实现任意list子类的同步,对大部分操作代码块进行加锁,例如:add,set,get,foreach等,不过迭代器操作未加锁,依旧是线程不安全的

3.使用Java集合的stream().map().collect(Collectors.toList())方法,利用Fork/Join的分治,每个线程会创建一个独立list进行操作,最后合并

4.CopyOnWriteArrayList,写操作单独创建一个list,读操作不加锁,适合读操作较多的场景

以上是关于JavaArrayList线程不安全的坑的主要内容,如果未能解决你的问题,请参考以下文章

这些线程安全的坑,你在工作中踩了么?

Java ArrayList / String / atomic 变量读取线程安全吗?

这些并发容器的坑,你要谨记!

FMDB的线程安全

django 多线程 + uWSGI 多线程 遇到的坑

单例模式