多线程中ArrayList调用Add()添加元素时的下标越界问题(java.lang.ArrayIndexOutOfBoundsException)

问题:

在多线程中使用ArrayList调用Add()添加元素时,有时候会出现下面的错误

Exception in thread "Thread-1" Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 15
    at java.util.ArrayList.elementData(ArrayList.java:418)
    at java.util.ArrayList.get(ArrayList.java:431)
    at com.jant.demo_jant.DemoJantApplication$AddToTest.run(DemoJantApplication.java:35)
    at java.lang.Thread.run(Thread.java:745)
java.lang.ArrayIndexOutOfBoundsException: 15
    at java.util.ArrayList.add(ArrayList.java:459)
    at com.jant.demo_jant.DemoJantApplication$AddToTest.run(DemoJantApplication.java:32)
    at java.lang.Thread.run(Thread.java:745)

那么问题来了,ArrayList是自动扩容、没有长度限制,为什么还会出现数组下标越界这种错误呢?

示例代码:

通过这个示例,来研究一下

public class DemoJantApplication {

    public static List<Integer> numberlist = new ArrayList<>();

    class AddToTest implements Runnable {

        int startNum = 0;

        public AddToTest(int startNum) {
            this.startNum = startNum;
        }

        @Override
        public void run() {
            int count = 0;
            while (count < 100) {

                try {
                    java.lang.Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                numberlist.add(startNum);
                System.out.println(java.lang.Thread.currentThread().getName() + "--" + (count) + "times"
                        +" 被添加的数字:"+ startNum
                        + ",list的最后数字:" + numberlist.get(numberlist.size() - 1)
                        + ",集合的大小:" + numberlist.size()
                        + ",list" + numberlist.toString());

                startNum += 2;
                count++;
            }
        }
    }

    public static void main(String[] args) {
        Thread t = new Thread(new DemoJantApplication().new AddToTest(0));
        Thread t1 = new Thread(new DemoJantApplication().new AddToTest(100));
        t.start();
        t1.start();
    }
}

打印出的log,我把他分为三种情况:

情况一:

这种情况算是正常情况,因为元素没有丢失:

线程1和线程2,第0次执行,但是集合的大小都是2

Thread-1--0times 被添加的数字:0,list的最后数字:100,集合的大小:2,list[0, 100]
Thread-2--0times 被添加的数字:100,list的最后数字:100,集合的大小:2,list[0, 100]
Thread-1--1times 被添加的数字:2,list的最后数字:2,集合的大小:4,list[0, 100, 102, 2]
Thread-2--1times 被添加的数字:102,list的最后数字:2,集合的大小:4,list[0, 100, 102, 2]
Thread-1--2times 被添加的数字:4,list的最后数字:4,集合的大小:5,list[0, 100, 102, 2, 4]

情况二:

线程1和线程2,第0次执行,但是集合的大小都是1

Thread-1--0times 被添加的数字:0,list的最后数字:100,集合的大小:1,list[100]
Thread-2--0times 被添加的数字:100,list的最后数字:100,集合的大小:1,list[100]
Thread-1--1times 被添加的数字:2,list的最后数字:2,集合的大小:2,list[100, 2]
Thread-2--1times 被添加的数字:102,list的最后数字:102,集合的大小:3,list[100, 2, 102]
Thread-1--2times 被添加的数字:4,list的最后数字:104,集合的大小:4,list[100, 2, 102, 104]

情况三:

添加元素出现错误

Exception in thread "Thread-1" Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 15
    at java.util.ArrayList.elementData(ArrayList.java:418)
    at java.util.ArrayList.get(ArrayList.java:431)
    at com.jant.demo_jant.DemoJantApplication$AddToTest.run(DemoJantApplication.java:35)
    at java.lang.Thread.run(Thread.java:745)
java.lang.ArrayIndexOutOfBoundsException: 15
    at java.util.ArrayList.add(ArrayList.java:459)
    at com.jant.demo_jant.DemoJantApplication$AddToTest.run(DemoJantApplication.java:32)
    at java.lang.Thread.run(Thread.java:745)

分析

首先,ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

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

先来看下ArrayList 使用add() 添加元素的流程:

1、添加元素,其中size是elementData数组中元组的个数,初始为0。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

2、确定list的容量,其作用为保证数组的容量始终够用

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              //DEFAULT_CAPACITY 为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

3、扩展list的长度,每次数组容量的增长大约是其原容量的1.5倍

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }

数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中

4、接下来回到Add()函数,继续执行,elementData[size++] = e;

当添加一个元素的时候,它可能会有两步来完成:

  1. 如果需要增大 Size 的值。
  2. 在 elementData[Size] 的位置存放此元素;

下面针对示例的三种情况逐一分析:

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

情况一:

执行顺序,在多线程情况下

线程 A 向ArrayList添加元素,位置0 。

线程 B 向ArrayList添加元素,位置1

线程A执行打印代码

线程B执行打印代码

情况二:

如果是在多线程情况下,

1、刚开始ArrayList 大小为0,首次扩充大小为10 (通过size获得的是实际存储数据的大小)
2、线程 A 添加元素,此时size 为0。在准备执行elementData[0] = e; 之后执行size++时 CPU 调度线程A暂停
3、线程B 添加元素,此时size 为0,执行了elementData[0] = e; 之后执行size++,此时size为1.
4、此时线程A继续执行size++ ,此时size为1.

所以线程B添加的值会被线程A添加的值覆盖

情况三:

观察发生越界时的数组下标,分别为10、15、22、33、49和73。
结合前面讲的数组自动机制,数组初始长度为10,第一次扩容为15=10+10/2,第二次扩容22=15+15/2,第三次扩容33=22+22/2…以此类推,通过不断调试可以发现,越界异常都发生在数组扩容之时。

1、 当集合中已经添加了14个元素时,线程A率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后线程A被阻塞在此处。

2、接着线程B进入add()方法,执行ensureCapacityInternal(size + 1),由于线程A并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。

3、 此时线程A运行,开始执行elementData[size++] = e;,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!


参考:

ArrayList在多线程调用Add()添加元素时的下标越界问题(java.lang.ArrayIndexOutOfBoundsException)

这篇博客写的不错,但是下面这段话分析不正确:

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

发布了244 篇原创文章 · 获赞 799 · 访问量 234万+
展开阅读全文

java.lang.ArrayIndexOutOfBoundsException: -1

09-18

这个怎么解决啊 弄了两天啦小白一个 ,做了一个推箱子的做了一半出错做不下去了,求助 public class MainFrame extends Frame implements KeyListener { public MainFrame() { tagetinit(); //笼子 personinit();//人物 Boxinit();//箱子 Treeinit();//树 backgroundInde();//背景初始化 setMainFrameUI();//主窗体 this.addKeyListener(this); //键盘监听器 } //用二维数组定义障碍 int[][] datas = { {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1}, {1,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1}, {1,0,1,1,0,1,1,0,0,1,1,0,0,0,0,1}, {1,0,1,1,0,1,1,0,0,1,1,0,0,0,0,1}, {1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,1}, {1,0,1,1,0,1,1,0,0,0,0,0,0,0,0,1}, {1,0,1,1,0,1,1,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} }; int wx; int wy; private void Treeinit() { Icon ic =new ImageIcon("tree.png"); //二维数组的遍历 for(int i=0;i<datas.length;i++) { for(int j=0;j<datas[i].length;j++) { if(datas[i][j] ==1) { JLabel lab_tree=new JLabel(ic); lab_tree.setBounds(12+50*j, 36+50*i, 50, 50); this.add(lab_tree); } } } } //笼子 private void tagetinit() { Icon i = new ImageIcon("lz.png"); JLabel lab_lz =new JLabel(i); this.add(lab_lz); lab_lz.setBounds(612, 436, 50, 50); JLabel lab_lz2 =new JLabel(i); lab_lz2.setBounds(612, 486, 50, 50); this.add(lab_lz2); JLabel lab_lz3 =new JLabel(i); lab_lz3.setBounds(612, 386, 50, 50); this.add(lab_lz3); } //背景 private void backgroundInde() { Icon i =new ImageIcon("bg1.jpg"); JLabel lag_bg = new JLabel(i); //设置位置与大小 lag_bg.setBounds(12,36,800,600); this.add(lag_bg); } //窗口 private void setMainFrameUI() { this.setLayout(null);// 设置自由布局 this.setTitle("推箱子1.1");//设置窗口名称 this.setLocation(300, 400); //窗口的位置 this.setSize(850, 650); //窗口大小 this.setVisible(true); //窗口可见 } // 人物 private void personinit() { int wx=1; int wy=8; Icon i = new ImageIcon("htl-zm.png"); lab_person = new JLabel(i); lab_person.setBounds(12+wx*50,36+wy*50,50,50 ); //设置位置 this.add(lab_person); //添加 } JLabel lab_person;//本来该法放在方法里面 放在这里让变量的作用范围变大一级 //箱子 private void Boxinit() { Icon i=new ImageIcon("lyy.png"); JLabel lab_box1= new JLabel(i); lab_box1.setBounds(212, 236, 50, 50); this.add(lab_box1); JLabel lab_box2= new JLabel(i); lab_box2.setBounds(412, 236, 50, 50); this.add(lab_box2); JLabel lab_box3= new JLabel(i); lab_box3.setBounds(612, 236, 50, 50); this.add(lab_box3); } //键盘监听器 @Override public void keyPressed(KeyEvent e) { //左 37 上38 右39 下40 int key = e.getKeyCode(); //用于获取键码值 //右移 if(key == 39) { if(datas[wy][wx+1] == 1) { return; } wx=wx+1; int x=(int)lab_person.getLocation().getX(); int y=(int)lab_person.getLocation().getY(); lab_person.setLocation(x+50,y); Icon i = new ImageIcon("htl-right.png"); lab_person.setIcon(i); } //左移 if(key == 37) { if(datas[wy][wx-1] == 1) { return; } wx=wx-1; //让人物移动首先要知道人物的位置 int x=(int)lab_person.getLocation().getX(); //得到的是走上叫 int y=(int)lab_person.getLocation().getY(); //确定一步移动多少 //右移x增加 y不变 lab_person.setLocation(x-50,y); //人物移动会改变方向 也就是改变一张图片 Icon i = new ImageIcon("htl-left.png"); lab_person.setIcon(i); } //上移 if(key == 38) { if(datas[wy-1][wx] == 1) { return; } wy=wy-1; int x=(int)lab_person.getLocation().getX(); int y=(int)lab_person.getLocation().getY(); lab_person.setLocation(x,y-50); Icon i = new ImageIcon("htl-back.png"); lab_person.setIcon(i); } //下移 if(key == 40) { if(datas[wy+1][wx]==1) { return; } wy=wy+1; int x=(int)lab_person.getLocation().getX(); int y=(int)lab_person.getLocation().getY(); lab_person.setLocation(x,y+50); Icon i = new ImageIcon("htl-zm.png"); lab_person.setIcon(i); } } @Override public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } } 问答

Arrayindexoutofboundsexception 异常

05-22

这个问题是关于android中customListview 的。我想使用onclick事件(tv.setOnClickListener)在list中动态的添加内容。我有自定义的country类使用geter and seter 方法来储存数据。当我动态的添加新的内容,然后滚动到list的底部时,获取 Arrayindexoutofboundexception 异常。 public class PosterList extends Activity { MyCustomAdapter dataAdapter = null; ArrayList<Country> countryList = new ArrayList<Country>(); TextView tv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.posterlist); //Click on textview to add element in contrylist tv = (TextView) findViewById(R.id.myFilter); tv.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Country country = new Country("df","df","df","m"); countryList.add(country); dataAdapter.notifyDataSetChanged(); } }); displayListView(); } private void displayListView() { //Parse my JSON and store it in to different arrays for(int k=0;k<len;k++) { Country country = new Country(subcategory[k],caseid[k],time[k],newpost[k]); countryList.add(country); } dataAdapter = new MyCustomAdapter(this,R.layout.country_info, countryList); ListView listView = (ListView) findViewById(R.id.listView1); listView.setAdapter(dataAdapter); listView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Country country = (Country) parent.getItemAtPosition(position); Toast.makeText(getApplicationContext(), country.getContinent(), Toast.LENGTH_SHORT).show(); } }); } private class MyCustomAdapter extends ArrayAdapter<Country> { private ArrayList<Country> originalList; private ArrayList<Country> countryList; public MyCustomAdapter(Context context, int textViewResourceId, ArrayList<Country> countryList) { super(context, textViewResourceId, countryList); this.countryList = new ArrayList<Country>(); this.countryList.addAll(countryList); this.originalList = new ArrayList<Country>(); this.originalList.addAll(countryList); } private class ViewHolder { TextView code; TextView name; TextView continent; TextView region; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; Log.v("ConvertView", String.valueOf(position)); if (convertView == null) { LayoutInflater vi = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.country_info, null); holder = new ViewHolder(); holder.code = (TextView) convertView.findViewById(R.id.code); holder.name = (TextView) convertView.findViewById(R.id.name); holder.continent = (TextView) convertView.findViewById(R.id.continent); holder.region = (TextView) convertView.findViewById(R.id.region); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Country country = countryList.get(position); holder.code.setText(country.getCode()); holder.name.setText(country.getName()); holder.continent.setText(country.getContinent()); holder.region.setText(country.getRegion()); return convertView; } } } 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 代码科技 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览