ArrayList底层源码及线程安全问题分析

ArrayList

一、简介

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

二、ArraList核心源码

 private static final long serialVersionUID = 8683452581122892189L;
 ​
     /**
      * 默认初始化容量 DEFAULT_CAPACITY 为10
      */
     private static final int DEFAULT_CAPACITY = 10;
 ​
     /**
      *  指定该ArrayList容量为0时,返回该空数组
      */
     private static final Object[] EMPTY_ELEMENTDATA = {};
 ​
     /**
      * 当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其内数据量为0。
      * 它与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回。
      */
     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 ​
     /**
      * The array buffer into which the elements of the ArrayList are stored.
      * The capacity of the ArrayList is the length of this array buffer. Any
      * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
      * will be expanded to DEFAULT_CAPACITY when the first element is added.
      */
     // 底层数组
     transient Object[] elementData; // non-private to simplify nested class access
 ​
     /**
      * 数组的长度
      */
     private int size;

ArrayList的构造函数

JDK8中ArrayList有三种方式来初始化:

 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);
         }
     }
 ​
     /**
      * 默认无参构造函数
      * DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
      */
     public ArrayList() {
         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
     }
 ​
     /**
      * Constructs a list containing the elements of the specified
      * collection, in the order they are returned by the collection's
      * iterator.
      *
      * @param c the collection whose elements are to be placed into this list
      * @throws NullPointerException if the specified collection is null
      */
     public ArrayList(Collection<? extends E> c) {
         elementData = c.toArray();
         if ((size = elementData.length) != 0) {
             // c.toArray might (incorrectly) not return Object[] (see 6260652)
             if (elementData.getClass() != Object[].class)
                 elementData = Arrays.copyOf(elementData, size, Object[].class);
         } else {
             // replace with empty array.
             this.elementData = EMPTY_ELEMENTDATA;
         }
     }

在无参构造函数中、初始化赋值的是一个空数组,当添加元素时,才真正分配容量。即向数组添加第一个元素时,容量为10。

ArrayList的扩容机制

add方法

  /**
      * 将指定的元素追加到此列表的末尾.
      *
      * @param e 添加到列表末尾的元素
      * @return <tt>true</tt> (as specified by {@link Collection#add})
      */
     public boolean add(E e) {
         // 扩容判断
         ensureCapacityInternal(size + 1);  // Increments modCount!!
         elementData[size++] = e;
         return true;
 }

add()方法调用了ensureCapacityInternal()方法

 private void ensureCapacityInternal(int minCapacity) {
         ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
     }

calculateCapacity()方法

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             return Math.max(DEFAULT_CAPACITY, minCapacity);
         }
         return minCapacity;
     }
  • 当添加第一个元素后,minCapacity经过Math.max()方法比较后,大小变为DEFAULT_CAPACITY即10.

ensureExplicitCapacity()方法

 // 判断是否需要扩容
     private void ensureExplicitCapacity(int minCapacity) {
         modCount++;
 ​
         // overflow-conscious code
         if (minCapacity - elementData.length > 0)
             // 调用grow方法进行扩容
             grow(minCapacity);
     }

分析:

  • 当add进第一个元素时,elementData.length为0。minCapacity经过Math.max()方法比较后,大小变为10。 此时调用grow()方法进行扩容。

  • 当add进第二个元素时,minCapacity为2,但是elementData.length在第一次扩容后变为10。 此时不进入grow()方法。

  • 当添加第11个元素时 minCapacity > elementData.length 。再进入grow()方法扩容。

grow()方法

 /**
      * ArrayList扩容的核心方法
      */
     private void grow(int minCapacity) {
         // overflow-conscious code
 ​
         // oldCapacity为旧容量,newCapacity为新容量
         int oldCapacity = elementData.length;
         // 将oldCapacity为旧容量右移一位,其效果相当于oldCapacity / 2
         // 我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍
         int newCapacity = oldCapacity + (oldCapacity >> 1);
         // 检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量作为新容量
         if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
         // 如果新容量大于MAX_ARRAY_SIZE,执行 `hugeCapacity()` 方法来比较minCapacity和MAX_ARRAY_SIZE
         /* 如果minCapacity大于最大容量,则新容量为 `Intergar.MAX_VALUE` ,否则,新容量大小为 MAX_ARRAY_SIZE
          * 即为 `Integer.MAX_VALUE - 8`
          */
         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);
     }

hugeCapacity()方法

 // 比较minCapacity 和 MAX_ARRAY_SIZE
     private static int hugeCapacity(int minCapacity) {
         if (minCapacity < 0) // overflow
             throw new OutOfMemoryError();
 ​
         // 对 minCapacity 和 MAX_ARRAY_SIZE进行比较
         // 若 minCapacity大,将 Integer.MAX_VALUE作为新数组大小
         // 若 MAX_ARRAY_SIZE大,将 MAX_ARRAY_SIZE 作为新数组大小
         // MAX_ARRAY_SIZE =  Integer.MAX_VALUE - 8;
         return (minCapacity > MAX_ARRAY_SIZE) ?
                 Integer.MAX_VALUE :
                 MAX_ARRAY_SIZE;
     }

三、代码测试

 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 ​
 public class AListTest {
 ​
     public static Integer getCapacity(ArrayList list){
         Integer length = null;
         Class c = list.getClass();
         Field f;
         try {
             f = c.getDeclaredField("elementData");
             f.setAccessible(true);
             Object[] o = (Object[]) f.get(list);
             length = o.length;
         }catch (NoSuchFieldException | IllegalAccessException e) {
             e.printStackTrace();
         }
         return length;
     }
 ​
     public static void main(String[] args) {
         // ArrayList常用方法及机制测试
         /**
          * ArrayList扩容机制测试
          */
         ArrayList<Integer> a = new ArrayList<>();
         System.out.println("空数组大小和容量分别是"+a.size()+"   "+getCapacity(a));
         a.add(1);
         System.out.println("空数组添加1个数据后,大小和容量分别是"+a.size()+"   "+getCapacity(a));
         a.add(2);
         System.out.println("空数组添加2个数据后,大小和容量分别是"+a.size()+"   "+getCapacity(a));
         for (int i = 1; i <= 8; i++) {
             a.add( i);
         }
         System.out.println("空数组添加到了10个数据后,大小和容量分别是"+a.size()+"   "+getCapacity(a));
         a.add(2);
         System.out.println("空数组添加11个数据后,大小和容量分别是"+a.size()+"   "+getCapacity(a));
     }
 }
 ​

四、高并发测试

我们知道ArrayList是线程不安全的,请编写一个不安全的案例并给出解决方案

1、线程不安全

 List<String> list = new ArrayList<>();
 ​
         for (int i = 1; i <= 30; i++) {
             new Thread(() -> {
                 list.add(UUID.randomUUID().toString().substring(0,8));
                 System.out.println(list);
             },String.valueOf(i)).start();
         }

 

报错:

 Exception in thread "Thread 10" java.util.ConcurrentModificationException

2.导致原因

并发非正常修改导致

一个人正在写入,另一个同学来抢夺,导致数据不一致,并发修改异常

3、解决方法:CopyOnWriteArrayList

 List<String> list = new Vector<>();//Vector线程安全
 List<String> list = Collections.synchronizedList(new ArrayList<>());//使用辅助类
 List<String> list = new CopyOnWriteArrayList<>();//写时复制,读写分离
 ​
 Map<String, String> map = new ConcurrentHashMap<>();
 Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

CopyOnWriteArrayList.add方法:

CopyOnWrite容器即写时复制,往一个元素添加容器的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,让后新的容器添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements),这样做可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值