ArrayList - 非线程安全
- 可以存储null
1)创建过程
未指定初始化容量时 - 默认创建一个空数组
指定了初始化容量 - 创建一个指定容量大小的数组
2)ArrayList是如何进行扩容的?
在向集合添加数据调用list.add()方法时,判断是否需要扩容
注意:JDK1.7之前ArrayList默认⼤⼩是10,JDk1.7之后是0
1)未指定集合容量,默认是0,若已经指定⼤⼩则集合⼤⼩为指定的;
2)当集合第⼀次添加元素的时候,集合⼤⼩扩容为10
int newCapacity = oldCapacity + (oldCapacity >> 1);
ArrayList的元素个数⼤于其容量时,扩容的⼤⼩ = 原始⼤⼩ + (原始⼤⼩ >> 1)
3)如果需要保证线程安全,ArrayList应该怎么做,⽤有⼏种⽅式?
1)⾃⼰写个包装类,根据业务⼀般是add/update/remove加锁
2)Collections.synchronizedList(new ArrayList<>()); 使⽤synchronized加锁
3)CopyOnWriteArrayList<>() 使⽤ReentrantLock加锁 - 写时复制技术
4)CopyOnWriteList<> - 写时复制容器
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
写时复制容器,在执行add()方法时,不是直接向Object[]容器添加元素,而是先将Object[]进行copy复制出一个新的容器Object[] newElements,然后往新的容器里添加元素,添加完之后再将原来容器的指向,指向新的元素 setArray(newElements);
这样的好处是,可以进行并发读,不需要加锁,因为当前元素不会直接执行添加操作,所以CopyOnWriteArrayList<>()是一种读写分离的思想,读和写不同的容器;
缺点:内存占⽤问题,写时复制机制,内存⾥会同时驻扎两个对象的内存,旧的对象和新写⼊的对象,如果对象⼤则容易发⽣Yong GC和Full GC