add() 方法的并发安全问题
一个共享的 ArrayList,在多线程并发条件下进行 add() 会出现线程安全问题:
示例:启动两个线程,并发的向同一个 ArrayList 中分别添加 10000 个数据:
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);
}
};
// 启动两个线程并发地向 list 中添加元素
for (int i = 0; i < 2; i++) {
new Thread(runnable).start();
}
Thread.sleep(500);
System.out.println(list.size());
}
理论上最后输出的结果应该为20000,但会出现以下两种结果:
1、list.size() 不为 20000
2、数组索引越界异常
问题分析:
分析 add() 方法的源码:
public boolean add(E e) {
// 若当前 ArrayList 中元素的数量为 size,则要确保 ArrayList 的容量至少为 size + 1
// 若 size+1 比当前 ArrayList 容量大,则扩容 grow()
ensureCapacityInternal(size + 1);
// 添加元素
elementData[size++] = e;
return true;
}
出现问题的原因1: elementData[size++] = e 不是一个原子操作,其本身包含两步:① elementData[size] = e ② size++
例如有两个线程,分别向 ArrayList 中加入数字 1 与 2:
假设现在size大小为1

问题一:
线程1 赋值 element[1] = 1;
线程2 赋值 element[1] = 2;
出现问题:后续线程会将前面线程写入的值覆盖
问题二:
线程1 自增 size++;(size=2)
线程2 自增 size++;(size=3)
出现问题:某些位置会没有值
因为原size=1,但是因为线程1与线程2都将值赋值给了 element[1],导致了 element[2] 内没有值为null,被跳过了,后续添加时,会直接向 element[3] 中添加数据。
出现问题的原因2:add()方法本身不是原子的
假设此时 ArrayList size = 9(即其中有9个元素)。ArrayList 的当前容量为 elementData.length = 10

t1进入add()方法,这时size为9,调用ensureCapacityInternal()方法:发现自己的需求为size+1=10,容量足够,无需扩容
t2进入add()方法,这时size为9,调用ensureCapacityInternal()方法:发现自己的需求为size+1=10,容量足够,无需扩容
t1开始设置元素,elementData[size++] = e,成功,此时size变为10
t2也开始设置元素,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9(容量为10)。于是此时会报出一个数组越界的异常:ArrayIndexOutOfBoundsException
(待…)
参考博客:https://blog.csdn.net/xsjzn/article/details/124361000
https://blog.csdn.net/zengsao/article/details/118857418

2202

被折叠的 条评论
为什么被折叠?



