ArrayList是否线程安全?如何线程安全地操作ArrayList?

1,概述
Arraylist是一个有序的集合,底层是基于数组实现的。所以查询速度快,但是增删相对较慢。而且是一个非线程安全的集合。下面我们结合源码看一下为什么非线程安全。

2,源码分析:

public boolean add(E e) {

    /**
     * 添加一个元素时,做了如下两步操作
     * 1.判断列表的capacity容量是否足够,是否需要扩容
     * 2.真正将元素放在列表的元素数组里面
     */
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal()这个方法的详细代码我们可以暂时不看,它的作用就是判断如果将当前的新元素加到列表后面,列表的elementData数组的大小是否满足,如果size + 1的这个需求长度大于了elementData这个数组的长度,那么就要对这个数组进行扩容。

由此看到add元素时,实际做了两个大的步骤:
1.判断elementData数组容量是否满足需求
2.在elementData对应位置上设置值

这样也就出现了第一个导致线程不安全的隐患,在多个线程进行add操作时可能会导致elementData数组越界。具体逻辑如下:

1⃣️,列表大小为9,即size=9
2⃣️,线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
3⃣️,线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
4⃣️, 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
5⃣️,线程B也发现需求大小为10,也可以容纳,返回。 线程A开始进行设置值操作, elementData[size++] = e
6⃣️,操作。此时size变为10。 线程B也开始进行设置值操作,它尝试设置elementData[10] =e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException

.另外第二步 elementData[size++] = e 设置值的操作同样会导致线程不安全。从这儿可以看出,这步操作也不是一个原子操作,它由如下两步操作构成:

elementData[size] = e;
size = size + 1;

3,解决方案:
1,vector 或着 SynchronizedList

SynchronizedList的兼容性比vector更好,但两者都是所有方法带同步锁,读写多的情况下效率非常低。所以并不是很好的方案。

2,java.util.concurrent.CopyOnWriteArrayList
CopyOnWrite(简称:COW):即复制再写入,jdk1.5后加入的,就是在添加元素的时候,先把原 List 列表复制一份,再添加新的元素。
先来看下它的 add 方法源码:

public boolean add(E e) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原始集合
Object[] elements = getArray();
int len = elements.length;
// 复制一个新集合
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 替换原始集合为新集合
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}

添加元素时,先加锁,再进行复制替换操作,最后再释放锁。

再来看下它的 get 方法源码:

private E get(Object[] a, int index) {
    return (E) a[index];
}

public E get(int index) {
    return get(getArray(), index);
}

可以看到,获取元素并没有加锁。

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值