最近看到书上讲到并发下ArrayList是不安全的可能会导致越界,多线程冲突访问的问题.建议改进的方法是使用vector 代替 ArrayList。于是乎脑袋里浮现出几个问题:
1.Arraylist是如何导致越界的问题?
2.vector是如何保证线程的安全的?
3.使用vector线程就一定安全吗?
4.vector和ArrayList分别适合在什么场景下使用
1.ArrayList如何导致越界问题?
1.先看一下错误代码
import java.util.ArrayList;
public class ArrayListMultiThread {
static ArrayList<Integer> al = new ArrayList<Integer>(10);
public static class AddThread implements Runnable {
public void run() {
for (int i = 0;i <1000000; i++) {
al.add(i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AddThread());
Thread t2 = new Thread(new AddThread());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(al.size());
}
}
运行结果如下这里出现了越界
2.如何导致越界?
1.先看一下需要用到的源码
首先,ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
从上面代码的报错中,我们可以看到在add方法那里出了问题,查看ArrayList中的add源码方法如下:
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
//添加元素之前,先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1);
//这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++] = e;//将对象e放在数组的末尾
return true;
}
可以看出add方法首先调用了ensureCapacityInternal的方法,这个方法是用来得到最小的扩容量。我们再来看看ensureCapacityInternal方法:
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是在jdk1.8中新加的。作用是根据第一个元素被添加时知道多少个元素去填充把它与EMPTY_ELEMENTDATA区分开来.
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取默认的容量和传入参数的较大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
//源码中写道 private static final int DEFAULT_CAPACITY = 10;
}
ensureExplicitCapacity(minCapacity);
}
ensureCapacituInternal方法的最后调用了ensureExplicitCapacity方法,这个方法是用来判断是否需要扩容。我们在来看一下这个方法:
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //表现出list结构上被修改的次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
需要的源码差不多都在上面了grow方法就暂时不做描述了。
2.ArrayList越界的逻辑
1.假设列表大小为9,即size=9
2.线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
3.线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
4.线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
5.线程B也发现需求大小为10,也可以容纳,返回。
6.线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
7.线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩 容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException
3.ArrayList导致多线程冲突访问。
执行上面的代码我们会发现有的时候不会报错,但是结果会远小于正确值。这是由于多线程访问冲突,使的保存容器大小的变量被多线程不正常的访问,同时两个线程也同时对ArrayList中的同一个位置进行复制导致的。
2. vector是如何保证线程安全的呢?
上述的错误代码如果将ArrayList都换成vector,执行的结果就是正确的了。那么vector是如何保证线程的安全的呢?看源码发现vector中一些关键的方法都用synchronized修饰,这就使得每次只能有一个线程去访问,从而保证安全。
3. 使用vector一定能保证线程的安全吗?
很多人都会问这个问题,答案是不安全的,虽然很多方法都用synchronized修饰,保证所有对外的接口都以vector为锁,但是在单个方法的原子性方面,并不能保证符合操作的原子性。对于复合操作vector和ArrayList一样都需要进行同步处理。所以,如果是这样的话,那么用vector和ArrayList就没有区别了。因为 synchronized 的开销是巨大的,vector还会导致一些性能下降的问题。
书上说建议使用vector代替ArrayList,看完别人的讲解发现vector还是不能乱用的,现在挺迷糊的。
4.vector和ArrayList分别适合在什么场景下使用
这个问题暂时未找到答案。o(╥﹏╥)o