zoukankan      html  css  js  c++  java
  • 关于ArrayList的越界问题?

    大家都知道 ArrayList是自动扩容的。 那为什么会存在越界问题?  

    话不多说 上代码

     1 package test;
     2 
     3 import java.util.ArrayList;
     4 
     5 public class ThreadUnSafe {
     6     public  static ArrayList<Integer> numberList= new ArrayList<Integer>();
     7     public  static  class  addToList implements Runnable{
     8         int startNum=0;
     9         public  addToList(int startNum){
    10             this.startNum=startNum;
    11         }
    12         @Override
    13         public  void  run(){
    14             int count=0;
    15             while (count<50){
    16                 try{
    17                     Thread.sleep(100);
    18                 }catch (InterruptedException e){
    19                     e.printStackTrace();
    20                 }
    21                 numberList.add(startNum);
    22                 System.out.println(Thread.currentThread().getName()+"=="+"第"+(count+1)+"次进入,添加的数子为"+numberList.get(numberList.size()-1)+"---此时集合大小为:"+numberList.size());
    23                 startNum+=2;
    24                 count++;
    25             }
    26         }
    27     }
    28 
    29     public static void main(String[] args) throws  InterruptedException{
    30         Thread t1=new Thread(new addToList(0));
    31         Thread t2=new Thread(new addToList(1));
    32         t1.start();
    33         t2.start();
    34 
    35     }
    36 
    37 }

    测试结果:

    Thread-1==第1次进入,添加的数字为1---此时集合大小为:1
    Thread-0==第1次进入,添加的数字为1---此时集合大小为:1
    Thread-0==第2次进入,添加的数字为1---此时集合大小为:2
    Thread-1==第2次进入,添加的数字为2---此时集合大小为:3
    Thread-0==第3次进入,添加的数字为2---此时集合大小为:4
    Thread-1==第3次进入,添加的数字为3---此时集合大小为:5
    Thread-0==第4次进入,添加的数字为4---此时集合大小为:7
    Thread-1==第4次进入,添加的数字为4---此时集合大小为:7
    Thread-0==第5次进入,添加的数字为4---此时集合大小为:8
    Thread-1==第5次进入,添加的数字为5---此时集合大小为:9
    Thread-1==第6次进入,添加的数字为6---此时集合大小为:10
    Thread-0==第6次进入,添加的数字为6---此时集合大小为:10
    Thread-0==第7次进入,添加的数字为6---此时集合大小为:11
    Thread-1==第7次进入,添加的数字为7---此时集合大小为:12
    Thread-1==第8次进入,添加的数字为8---此时集合大小为:14
    Thread-0==第8次进入,添加的数字为8---此时集合大小为:14
    Thread-1==第9次进入,添加的数字为9---此时集合大小为:15
    Thread-0==第9次进入,添加的数字为8---此时集合大小为:16
    Thread-1==第10次进入,添加的数字为10---此时集合大小为:17
    Thread-0==第10次进入,添加的数字为9---此时集合大小为:18
    Thread-0==第11次进入,添加的数字为10---此时集合大小为:19
    Thread-1==第11次进入,添加的数字为11---此时集合大小为:20
    Thread-0==第12次进入,添加的数字为11---此时集合大小为:21
    Thread-1==第12次进入,添加的数字为11---此时集合大小为:21
    Thread-1==第13次进入,添加的数字为13---此时集合大小为:22
    Thread-0==第13次进入,添加的数字为12---此时集合大小为:23
    Thread-1==第14次进入,添加的数字为14---此时集合大小为:25
    Thread-0==第14次进入,添加的数字为14---此时集合大小为:25
    Thread-1==第15次进入,添加的数字为15---此时集合大小为:26
    Thread-0==第15次进入,添加的数字为15---此时集合大小为:26
    Thread-0==第16次进入,添加的数字为15---此时集合大小为:28
    Thread-1==第16次进入,添加的数字为15---此时集合大小为:28
    Thread-1==第17次进入,添加的数字为16---此时集合大小为:29
    Thread-0==第17次进入,添加的数字为16---此时集合大小为:29
    Thread-0==第18次进入,添加的数字为18---此时集合大小为:31
    Thread-1==第18次进入,添加的数字为18---此时集合大小为:31
    Thread-0==第19次进入,添加的数字为18---此时集合大小为:32
    Thread-1==第19次进入,添加的数字为18---此时集合大小为:32
    Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 33
    at java.util.ArrayList.elementData(ArrayList.java:422)
    at java.util.ArrayList.get(ArrayList.java:435)
    at test.ThreadUnSafe$addToList.run(ThreadUnSafe.java:22)
    at java.lang.Thread.run(Thread.java:748)
    java.lang.ArrayIndexOutOfBoundsException: 33
    at java.util.ArrayList.add(ArrayList.java:463)
    at test.ThreadUnSafe$addToList.run(ThreadUnSafe.java:21)
    at java.lang.Thread.run(Thread.java:748)

    ?  为什么会有数组越界呢 。对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。

    其中:at java.util.ArrayList.add(ArrayList.java:463)的源代码

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }
     
     函数体中,modCount是数组发生size更改的次数。然后if判断,如果数组长度小于默认的容量10,则调用扩大数组大小的方法grow()。

    其中 函数grow()解释了基于数组的ArrayList是如何扩容的。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中
    每次数组容量的增长大约是其原容量的1.5倍。
     接下来回到Add()函数,继续执行,elementData[size++] = e; 这行代码就是问题所在,当添加一个元素的时候,它可能会有两步来完成:
    1. 在 elementData[Size] 的位置存放此元素;
    2. 增大 Size 的值。

    在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

        如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了

    我猜想是,由于没有该方法没有同步,导致出现这样一种现象,用第一次异常,即下标为15时的异常举例。当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。而刚刚阻塞在elementData[size++] = e;语句之前的线程开始执行,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!

  • 相关阅读:
    Django请求的生命周期图解及流程
    Django中请求的生命周期
    127.0.0.1和0.0.0.0和本机IP的区别
    Linux中errno的含义
    wireshark 过滤表达式
    GDB调试
    【LinuxShell】grep用法详解
    Linux下 tftp 服务器的安装与使用
    设备掐断重启
    GDB disassemble
  • 原文地址:https://www.cnblogs.com/smellpawn/p/10841480.html
Copyright © 2011-2022 走看看