浅谈java1.8ArrayList源码(扩容机制,快速失败机制)

  • 首先:ArrayList的底层通过数组实现

public class ArrayList extends AbstractList

        implements List, RandomAccess, Cloneable, java.io.Serializable

  • ArrayList:说明ArrayList支持泛型。
  • extends AbstractList :继承了AbstractList。AbstractList提供List接口的骨干实现,以最大限度地减少“随机访问”数据存储(如ArrayList)实现Llist所需的工作。
  • implements List:实现了List。实现了所有可选列表操作。
  • implements RandomAccess:表明ArrayList支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
  • implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。
  • implements java.io.Serializable:表明该类具有序列化功能。有序列化功能

   下面来看一些比较重要的参数:

  初始化的默认容量

指定该ArrayList数组容量为零时返回该对象数组


  默认调用空构造方法时返回该对象数组


   ArrayList底层数组中存储真正元素的数组


   ArrayList的实际大小(数组包含真正的元素个数)


下面是ArrayList的三种构造方法:

第一种:


 执行完构造方法时还是一个空数组,等到add方法执行的时候则会初始化容量为10;

第二种:

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第1张图片

自己穿入想要的容量参数,对容量进行判断如果容量小于零,则会抛出异常,否则会创建一个容量为initialCapacity的空ArrayList

第三种:

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第2张图片

构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

下面来看ArrayList的扩容机制()

先来看其中的add()方法:

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第3张图片

add方法首先会调用ensureCapacityInternal(size+1)方法,

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第4张图片

ensureCapacityInternal(size+1)方法是用来进行容量检查,决定扩容的想要的最小容量,举个例子,第一次扩容的时候,size默认为0则minCapacity(即想要的最小容量)为1,执行完Math.max语句minCapacity就变为10,这个时候也就是进行空构造第一次add的情况,当ensureCapacityInternal(size+1)传入的参数小于默认参数的时候,就把默认参数当做想要的最小容量,如果大于默认参数就把你想要的参数当做想要的最小容量

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第5张图片

这个方法是用来判断是否扩容,如果你想要的最小容量大于数组长度则会调用grow方法进行扩容

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第6张图片

通过grow方法可以看到新容量为当前数组容量的1.5倍,然后进行判断,如果理论扩容后新的容量小于小于你想要的最小容量(但我觉得这种情况不太可能会出现,因为为了节省空间,假如当前容量为10,只会传入11来和扩容后的进行比较因此我自己认为不会出现if为真的情况)真正的实现扩容其实是Arrays.copy方法,就是复制数组实现扩容,

浅谈java1.8ArrayList源码(扩容机制,快速失败机制)_第7张图片

但是如果出现了if为真的情况,从这个方法中可以看到数组的理论最大值为Integer的最大值减去8,但是如果你想要的最小容量已经大于数组的理论最大值,则会进行大容量分配为Integer的最大值至此扩容结束, 

下面粗略的谈一下快速失败机制(因为对线程还没有一个好的认识)

public class FastFailTest {

 private static List list = new ArrayList();
 //private static List list = new CopyOnWriteArrayList();
 public static void main(String[] args) {

  // 同时启动两个线程对list进行操作!
  new ThreadOne().start();
  new ThreadTwo().start();
 }

 private static void printAll() {
  System.out.println("");

  String value = null;
  Iterator iter = list.iterator();
  while(iter.hasNext()) {
   value = (String)iter.next();
   System.out.print(value+", ");
  }
 }

 /**
  * 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list
  */
 private static class ThreadOne extends Thread {
  public void run() {
   int i = 0;
   while (i<6) {
    list.add(String.valueOf(i));
    printAll();
    i++;
   }
  }
 }

 /**
  * 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list
  */
 private static class ThreadTwo extends Thread {
  public void run() {
   int i = 10;
   while (i<16) {
    list.add(String.valueOf(i));
    printAll();
    i++;
   }
  }
 }

}

fail-fast是怎么产生的。步骤如下:

  1. 新建了一个ArrayList,名称为arrayList。
  2. 向arrayList中添加内容
  3. 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。
  4. 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。
  5. 这时,就会产生有趣的事件了。

在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。 
在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!

“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

至此,我们就完全了解了fail-fast是如何产生的! 
即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

方法1
在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。看看ArrayList中迭代器的remove方法的源码:
[java]  view plain  copy
  1. public void remove() {  
  2.     if (lastRet < 0)  
  3.         throw new IllegalStateException();  
  4.     checkForComodification();  
  5.   
  6.     try {  
  7.         ArrayList.this.remove(lastRet);  
  8.         cursor = lastRet;  
  9.         lastRet = -1;  
  10.         expectedModCount = modCount;  
  11.     } catch (IndexOutOfBoundsException ex) {  
  12.         throw new ConcurrentModificationException();  
  13.     }  
  14. }  

可以看到,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响,因为该方法remove不能指定元素,只能remove当前遍历过的那个元素,所以调用该方法并不会发生fail-fast现象。该方法有局限性。
例子:
[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.       List list = new ArrayList<>();  
  3.       for (int i = 0 ; i < 10 ; i++ ) {  
  4.            list.add(i + "");  
  5.       }  
  6.       Iterator iterator = list.iterator();  
  7.       int i = 0 ;  
  8.       while(iterator.hasNext()) {  
  9.            if (i == 3) {  
  10.                 iterator.remove(); //迭代器的remove()方法  
  11.            }  
  12.            System.out.println(iterator.next());  
  13.            i ++;  
  14.       }  
  15. }  
方法2
使用java并发包(java.util.concurrent)中的类来代替 ArrayList 和hashMap。
比如使用  CopyOnWriterArrayList代替 ArrayList, CopyOnWriterArrayList在是使用上跟 ArrayList几乎一样, CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于 CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
对于HashMap,可以使用ConcurrentHashMap, ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。


你可能感兴趣的:(浅谈java1.8ArrayList源码(扩容机制,快速失败机制))