java多线程异步操作ArrayList出现数组越界异常问题

java多线程异步操作ArrayList出现数组越界异常问题

今天想通过多线程向集合中添加元素来测试线程池的性能时,想当然的使用了List的ArrayList的实现类。结果出现了数组越界异常,知道ArrayList底层是数组实现的小伙伴可能会认为难道ArrayList也有容量上限??可是我的测试也就100000个,所以不可能是达到ArrayList的上限问题。后来发现经常记的"ArrayList是线程不安全的"那句八股文真的是在自己脑海里就是一段有记忆但没有用的废h话。。。。。

所以此次线程操作ArrayList引发的数组越界异常就是==“ArrayList线程不安全”==引起的。那为什么ArrayList为什么是线程不安全的呢?这还要和ArrayList扩容机制说起。关于ArrayList的扩容机制请看我的这篇文章

ArrayList的底层实现和扩容机制

我们都知道ArrayList底层是数组,那我们来看看源码中这个数组是如何定义的。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
     private static final Object[] EMPTY_ELEMENTDATA = {};
     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
     transient Object[] elementData; //transient是防止字段序列化
}

数组在构造器中初始化

//有参
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
//无参
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

显而易见,数组在初始定义时就没有添加一些同步关键字(如:synchronized)进行限制。这还不是最致命的,来看看它的扩容方法(扩容的逻辑代码段比较长,这里裁剪了比较重要的一部分)。

//以下两个方法是扩容机制的逻辑
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
//下面的方法是扩容的实现
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

从扩容逻辑到扩容实现可见,所有的方法都没有添加线程同步限制。所以在多线程异步操做ArrayList时,由于不同线程中扩容逻辑代码段中的参数不统一,造成了数组该扩容的时候没有实时扩容,造成了ArrayList底层维护的数组发生了越界。这也是问题的出现。

知道了问题存在,解决方法也就出来了,换个List接口的实现类。但是后来想一想使用LinkList实现类可以吗?该实现类底层是基于链表实现的,不存在数组下标越界异常,此时八股文"LinkedList也是线程不安全的"缓缓在脑中飘过。。。。

public class Test1 {
    public static void main(String[] args) {
		//List<Integer> list = new ArrayList<>();
        List<Integer> list = new LinkedList<>();
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 100000; i++) {
            threadPool.execute(new MThread(list));
        }
        System.out.println("集合中的个数为: " + list.size());
        threadPool.shutdown();
    }
}

class MThread implements Runnable {

    static Random random = new Random();
    private List<Integer> list;

    public MThread(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        list.add(random.nextInt());
    }
}
//运行结果:集合中的个数为: 92979

但我还是想试一试会发生什么,于是将List的实现类换成了LinkedList,结果瞬间让我拉回了刚学线程的那会,集合中的数据个数没有达到预期值。so….这个集合的实现类必须满足线程安全。所以这里改用Vector(由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在Vector已被弃用)。问题解决!

总结:实践出真知,八股文有时候可以帮我们快速定位到bug,但是只是一味的记忆不去联想应用于实际,就等于自己记忆了一段废话。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值