ArrayList线程是否安全?以及解决方案「建议收藏」

ArrayList线程是否安全?以及解决方案「建议收藏」1,概述Arraylist是一个有序的集合,底层是基于数组实现的。所以查询速度快,但是增删相对较慢。而且是一个非线程安全的集合。下面我们结合源码看一下为什么非线程安全。2,源码分析:publicbooleanadd(Ee){/***添加一个元素时,做了如下两步操作*1.判断列表的capacity容量是否足够,是否需要扩容*2.真正将元素放在列表的元素数组里面*/ensureCapacityInternal(size+

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);
}

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

这样做的好处是,在高并发情况下,读取元素时就不用加锁,写数据时才加锁,大大提升了读取性能。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/10834.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注