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