图解ArrayList

ArrayList的数据结构

在这里插入图片描述

ArrayList的初始化

可以通过有参构造函数传入int类型的初始容量值,如果未传入初始容量,通过无参构造进行初始化,这时候初始化的是个空的数组,只有在第一次add方法时,才会进行扩容,默认的初始容量是10。

ArrayList的扩容

在这里插入图片描述

ArrayList的插入

1.不需要扩容

在这里插入图片描述

2.需要扩容

若检查发现需要扩容,则先对原数组进行扩容,扩容后在新数组上进行插入操作,如上图所示。

ArrayList的删除

在这里插入图片描述

ArrayList的线程不安全问题

public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(list);
            }).start();
        }
        Thread.sleep(500);
        System.out.println(list.size());
  }

在这里插入图片描述
在上面的代码中,定义了十个线程,在每个线程里分别向集合执行add操作。从打印的结果可以发现两个问题(多执行几次): 第一个位置并没插入元素;抛出了并发修改的异常

1.为什么会出现第一个位置被跳过的情况

从add方法的源代码入手,代码主要两个步骤,第一步:判断集合容量够不够,如果不够则进行扩容;第二步:根据size找到要放入元素的位置,后将size进行自增;

public boolean add(E e) {
		//第一步 检测是否需要扩容 如果需要则进行扩容
        ensureCapacityInternal(size + 1);
        //第二步 插入元素 并自增size
        elementData[size++] = e;
        return true;
}

下面,为了简化说明,以两个线程作示意图:
因为没有加锁,所以两个线程同时进入add方法,并判断需要对集合进行扩容
在这里插入图片描述
线程1首先完成扩容,并将元素插入下标为0的位置,size自增变成1

在这里插入图片描述
此时线程2也完成了扩容,并修改了Object数组引用指向的位置,导致线程1的数据丢失。但是由于size是全局的变量,已经被线程1修改成了1,所以在插入元素时,数组的下标为0的位置就被跳过了,直接插到了第二个位置,这就是第一个值为null的原因
在这里插入图片描述

2.抛出ConcurrentModificationException异常

在聊这个问题之前,先讲讲什么是Fail-Fast机制(快速失败机制),先看代码,在foreach中执行add操作就会抛出ConcurrentModificationException异常

public static void main(String[] args) throws InterruptedException {

        List<String> list = new ArrayList();
        list.add("C");
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("F");
        for (String string : list) {
            list.add("D");

        }
}

简单来说,在ArrayList中有个变量叫modCount(修改次数计数),每次集合的结构被修改(add,remove,sort),它的值就会进行自增。
在执行foreach前,会有个变量叫expectedModCount(期望修改次数计数),它的初始值与modCount相等。
在foreach中每次都会判断modCount与expectedModCount是否相等,如果不相等则抛出ConcurrentModificationException。在第一次循环中执行了add方法导致modCount自增,所以在第二次循环中modCount与expectedModCount不相等,抛出异常。

回到主线问题,我们在每个线程里面都执行了System.out.println(list)

for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 4));
                System.out.println(list);
            }).start();
}

在它里面会调用父类AbstractCollection的toString方法,这个方法也会涉及到元素的遍历,当其他线程执行集合的add方法导致modCount自增,所以在当前线程遍历时modCount与expectedModCount不相等,抛出ConcurrentModificationException异常

public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
}
public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
}
public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值