2.8.2 并发下的ArrayList,以及源码分析

package 第二章.并发下的ArrayList;

import java.util.ArrayList;import java.util.List;

/** * Created by zzq on 2018/1/19. */public class 并发下的ArrayList {       static ArrayList<Integer> list=new ArrayList<Integer>();    public static  class  AddThread extends Thread{        @Override        public void run() {            for (int i = 0; i < 100000; i++) {                list.add(i);            }            }

}    public static void main(String[] args) throws InterruptedException {       //ArrayList 网上介绍有容量上限 大约8GB左右       //扩容的公式  ((旧容量 * 3) / 2) + 1 注:这点与C#语言是不同的,C#当中的算法很简单,是翻倍。      /*  一旦容量发生变化,就要带来额外的内存开销,和时间上的开销。        所以,在已经知道容量大小的情况下,推荐使用下面方式进行声明:        List arrayList = new ArrayList(CAPACITY_SIZE);        即指定默认容量大小的方式。        */        Thread t1=new Thread(new AddThread());        Thread t2=new Thread(new AddThread());        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println(list.size());        /*上面的程序经过我多次测试得出3个结果          1:size为200000(理想情况)          2:size为小于200000(多次) 因为ArrayList 是一个线程不安全的容器          3:会报异常(这是由于ArrayList在扩容的时候,内部一致性被破坏了,由于没有所的保护,                        另一个线程访问到了不一致的背部状态,导致越界问题)          Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 2776         at java.util.ArrayList.add(ArrayList.java:441)          at 第二章.程序的幽灵之隐蔽的错误.并发下的ArrayList$AddThread.run(并发下的ArrayList.java:15)          at java.lang.Thread.run(Thread.java:745)         */    }}

1.ArrayListde add()方法:(源码分析)         public boolean add(E e) { @ 备注1:              ensureCapacityInternal(size + 1);  // @ 备注2:              elementData[size++] = e;                return true;            } @ 备注1:解析       其中   ensureCapacityInternal(size + 1)方法是 比较当前list的长度和该list的容量,我们进入该方法继续观看

private void ensureCapacityInternal(int minCapacity) {                if (elementData == EMPTY_ELEMENTDATA) {//判断该list的容量 是否为空,如果为空,给它一个默认的值 10                    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);                }

ensureExplicitCapacity(minCapacity);            }

其中 ensureExplicitCapacity(minCapacity) 方法的作用 是判断改list是否需要扩容:我们进入该方法中查看:

private void ensureExplicitCapacity(int minCapacity) {               modCount++;//位置加1

// overflow-conscious code               if (minCapacity - elementData.length > 0) //如果当前的size大于此刻list的容量,那么就进行扩容。                   grow(minCapacity);           }       其中    grow(minCapacity) 方法为扩容方法(请看源码):

private void grow(int minCapacity) {                       // overflow-conscious code                       int oldCapacity = elementData.length;                       int newCapacity = oldCapacity + (oldCapacity >> 1);//容量为原来的1.5倍                       if (newCapacity - minCapacity < 0)                           newCapacity = minCapacity;                       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);//将此刻的list拷贝到一个新的扩容之后list中                   }

@ 备注2:经过以上的判断,然后 elementData[size++] = e; 将该元素加入到该list中

线程安全问题:

通过以上的分析,我们就知道,如果单线程中,是串行的,后面的操作是在之前的操作结束后再次执行,所以list的每一次add方法后都能保持list数据结构的一致

但是在多线程中:没有用到锁机制的前提下线程之间是并行的,那么我们开始分析2种错误形式:(以我们的测试案例来说明)

第一种:size为小于200000(多次)

在 @ 备注1的操作时候2个线程同时拿到一个位置,然后添加,这就会造成 实际是添加2条数据,但是由于 index值是一样的,那么我们实际存入的是一条

第二种:会报异常(数组越界)

发生这种情况,是因为2个线程同时拿到了最后一个位置,但是他们都认为这是最后一个位置,所以list没有进行扩容,所以在其中一个线程执行  @ 备注2 之后,另一个线程也执行了  @ 备注2

这就造成了,本来list的size为10,第二个线程添加的位置是11,所以就会报出 数组越界

原文地址:https://www.cnblogs.com/anxbb/p/8425454.html

时间: 2024-10-11 22:58:25

2.8.2 并发下的ArrayList,以及源码分析的相关文章

ArrayList部分源码分析(基于1.8)

今天分析第一个集合类:ArrayList 首先,说一下我读这部分源码的感受. ArrayList类底层实现实际上是数组,因此很多操作会调用很多本地(Native)方法来实现或者部分实现.用java实现的很多方法中,只是用java代码进行了一些必要的逻辑判断和变量值的改变. 在AbstractList中加入的modCount变量是为了配合迭代器的使用. 下面是部分源码分析: package java.util; import java.util.function.Consumer; import

Java——ArrayList底层源码分析

1.简介 ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中. 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制.移动.代价比较高.因此,它适合随机查找和遍历,不适合插入和删除. 线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1). ArrayL

ArrayList&lt;E&gt;源码分析

ArrayList是按照线性表结构实现的 ArrayList的主要继承结构 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public interface List<E> extends Collection<E> public interface Collect

ArrayList的源码分析(基于jdk1.8)

1.初始化 transient Object[] elementData; //实际存储元素的数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() { //初始化为一个默认的空数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 2. 添加元素 private static final int DEFAU

ArrayList的源码分析

/** Default initial capacity. 初始化容量为10 */private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = {};/** Shared empty array instance used for default sized empty instances. We distinguish this from EMPTY_ELEMEN

JAVA Collection 源码分析(一)之ArrayList

到今天为止,差不多已经工作一年了,一直在做的是javaweb开发,一直用的是ssh(sh)别人写好的框架,总感觉自己现在高不成低不就的,所以就像看看java的源码,顺便学习一下大牛的思想和架构,read and write一直是提高自己编程水平的不二法门,写博客只是记录自己的学习历程,方便回顾,写的不好的地方,请多多包含,不喜勿喷,好了废话少说,现在让我们开始我们的历程把,Let's go!!!!!!!! 想看源码无从下手,不知道有没有跟我一样感觉的人们,今天用Intellij发现了可以找出类与

Java中ArrayList源码分析

一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保证容量能容纳所有数据. 1.1.ArrayList 的继承与实现接口 ArrayList继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口. public class  ArrayList<E> ex

Java - ArrayList源码分析

java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.默认初始容量为10.随着ArrayList中元素的增加,它的容量也会不断的自动增长.在每次添加新的元素时,Array

Java集合框架之一:ArrayList源码分析

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! ArrayList底层维护的是一个动态数组,每个ArrayList实例都有一个容量.该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向 ArrayList 中不断添加元素,其容量也自动增长. ArrayList不是同步的(也就是说不是线程安全的),如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步,在多线程环境下,可以使用Collections.synch