ArrayList 在并发条件下的线程安全问题

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值