ArrayList为什么线程不安全

执行下面代码,程序何如??

package VolatilePkg;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author Heian
 * @time 19/01/22 10:27
 * @copyright(C) 2019 深圳市北辰德科技股份有限公司
 * 用途:ArrayList 线程为什么不安全?
 */
public class VolatileTest {
    static List<Integer> list = new ArrayList<> ();
    //static List<Integer> list = new Vector<> ();
    public  void add(){
        for (int i=0;i<1000;i++){
            list.add (1);
        }
    }

    public static void test() throws InterruptedException{
        VolatileTest vo = new VolatileTest ();
        IntStream.range (0,5).forEach (value -> new Thread (() -> vo.add (),String.valueOf (value)).start ());
        while (Thread.activeCount ()>1){
            Thread.yield ();
        }
        System.out.println (list.size ());

    }

    public static void main(String[] args) throws InterruptedException{
       VolatileTest.test ();
    }


}

如果线程安全则输出为5000,但输出结果有三种情况:

  1. 小于5000
  2. 5000
  3. 小于5000  + ArrayIndexOutOfBoundsException

可以看出ArrayList在多个线程操作的情况下,无法保证数据的完整性。对add源码分析:

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 1
        elementData[size++] = e;           // 2
        return true;
    }

ArrayList 的不安全主要体现在两个方面:摘自:https://www.jianshu.com/p/41be1efe5d65

原因一:标记2处不是一个原子操作,JMM只保证了基本读取和赋值的原子性,其它均不保证,此可以拆分为两步骤:

elementData[size] = e;
size++;

    单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。

  1. 列表为空 size = 0。
  2. 线程 A 执行完 elementData[size] = e;之后挂起。A 把 "a" 放在了下标为 0 的位置。此时 size = 0。
  3. 线程 B 执行 elementData[size] = e; 因为此时 size = 0,所以 B 把 "b" 放在了下标为 0 的位置,于是刚好把 A 的数据给覆盖掉了。
  4. 线程 B 将 size 的值增加为 1。
  5. 线程 A 将 size 的值增加为 2。

    这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 "a" 在下标为 0 的位置,"b" 在标为 1 的位置。而实际情况确是下标为 0 的位置为 "b",下标为 1 的位置啥也没有,为null
原因二:标记1处则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,因此便出现了数组越界异常。比如:ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。执行步骤如下:

  1. 线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。
  2. 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 "b" 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
  3. 线程 A 接着执行,尝试把 "a" 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常
扫描二维码关注公众号,回复: 6498818 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_40826106/article/details/86596068