ArrayList - 非线程安全

  • 可以存储null

arraylist的创建初始化.png

1)创建过程

未指定初始化容量时 - 默认创建一个空数组
arraylist初始化未指定容量代码.png
指定了初始化容量 - 创建一个指定容量大小的数组
arraylist初始化指定容量代码.png

2)ArrayList是如何进行扩容的?

在向集合添加数据调用list.add()方法时,判断是否需要扩容
注意JDK1.7之前ArrayList默认⼤⼩是10JDk1.7之后是0
1)未指定集合容量,默认是0,若已经指定⼤⼩则集合⼤⼩为指定的;
2)当集合第⼀次添加元素的时候,集合⼤⼩扩容为10
int newCapacity = oldCapacity + (oldCapacity >> 1);
ArrayList的元素个数⼤于其容量时,扩容的⼤⼩ = 原始⼤⼩ + (原始⼤⼩ >> 1)
arrayList扩容的核心方法.png

3)如果需要保证线程安全,ArrayList应该怎么做,⽤有⼏种⽅式?

1)⾃⼰写个包装类,根据业务⼀般是add/update/remove加锁
2)Collections.synchronizedList(new ArrayList<>()); 使⽤synchronized加锁
3)CopyOnWriteArrayList<>() 使⽤ReentrantLock加锁 - 写时复制技术

4)CopyOnWriteList<> - 写时复制容器

  1. public boolean add(E e) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. Object[] newElements = Arrays.copyOf(elements, len + 1);
  8. newElements[len] = e;
  9. setArray(newElements);
  10. return true;
  11. } finally {
  12. lock.unlock();
  13. }
  14. }

写时复制容器,在执行add()方法时,不是直接向Object[]容器添加元素,而是先将Object[]进行copy复制出一个新的容器Object[] newElements,然后往新的容器里添加元素,添加完之后再将原来容器的指向,指向新的元素 setArray(newElements);
这样的好处是,可以进行并发读,不需要加锁,因为当前元素不会直接执行添加操作,所以CopyOnWriteArrayList<>()是一种读写分离的思想,读和写不同的容器;
缺点:内存占⽤问题,写时复制机制,内存⾥会同时驻扎两个对象的内存,旧的对象和新写⼊的对象,如果对象⼤则容易发⽣Yong GC和Full GC