https://blog.csdn.net/weixin_45668482/article/details/117396603

在单线程开发环境中,我们经常使用ArrayList作容器来存储我们的数据,但它不是线程安全的,在多线程环境中使用它可能会出现意想不到的结果。

多线程中的ArrayList:

我们可以从一段代码了解并发环境下使用ArrayList的情况:

public class ConcurrentArrayList {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<>();

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

        for (int i = 0; i < 2; i++) {
            new Thread(runnable).start();
        }

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

代码中循环创建了两个线程,这两个线程都执行10000次数组的添加操作,理论上最后输出的结果应该为20000,但经过多次尝试,最后只出现了两种结果:

  1. 数组索引越界异常
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 10
	at java.util.ArrayList.add(ArrayList.java:463)
	at ConcurrentArrayList.lambda$main$0(ConcurrentArrayList.java:14)
	at java.lang.Thread.run(Thread.java:748)
10007

  1. 输出结果小于20000
16093

虽然仍有可能得到20000的结果,但概率非常低。我们要从ArrayList的源码中去分析为什么会出现这种结果。

ArrayList数组默认初始化大小:

// 默认初始大小
private static final int DEFAULT_CAPACITY = 10;
...
// 数组size
private int size;

ArrayList的add方法:


public boolean add(E e) {
    //确定集合的大小是否足够,如果不够则会进行扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

以上面错误1:ArrayIndexOutOfBoundsException: 10为例,出现错误的步骤如下:

  1. 假设某时刻Thread-0和Thread-1都执行到了elementData\[size++\] = e; 这步,获取的size大小都为9,此时轮到Thread-1执行
  2. Thread-1执行elementData\[9\] = e,空间刚刚好够用,赋值完后size变为10。接着轮到Thread-0执行
  3. 因为Thread-0已经跳过了ensureCapacityInternal(size + 1); 这步判断容量的检查步骤,因此它执行elementData\[10\] = e,而数组容量刚好为10!此时就出现了数组越界的错误。