并发下ArrayList为何会出现越界 & 为何用vector代替ArrayList & 使用vector线程一定是安全的嘛?

最近看到书上讲到并发下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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值