【问题标题】:ArrayList index out of bounds though the accessed index is within bounds尽管访问的索引在范围内,但 ArrayList 索引超出范围
【发布时间】:2016-05-07 13:07:19
【问题描述】:

我收到ArrayList 的索引越界异常。在异常点, size() 和 index 在打印时似乎很好。下面是相关代码。我有一个自定义表模型,单击按钮时会刷新。

public class CallingClass
{
    public void buttonClicked()
    {
        new Thread(new Runnable()
        {
            public void run()
            {                   
                dataTableModel.refresh();
            }
        }).start();     
    }
}

public class DataTableModel extends AbstractTableModel
{
    protected ArrayList<Object> data;   

    public DataTableModel()
    {
        data = new ArrayList<Object>();
    }

    public Object getValueAt(int modelRow, int modelColumn)
    {
         // Throws Index Out of Bounds Exception though a println here shows modelRow value within 0 to data.size()-1
         // DataObject is just an interface to support getValue method
        return ((DataObject) data.get(modelRow)).getValue();
    }

    public void refresh()
    {
        reloadData();
        fireTableDataChanged();
    }

    protected void reloadData()
    {
        ArrayList<TextMessage> messageList = jmsConnection.getMessageList();

        data.clear();

        try
        {
            for(int i=0; i<messageList.size(); ++i)
            {
                data.add(new MyDataObject(messageList.get(i).getJMSMessageID()));
            }
        }
        catch(Exception e)
        {
        }
    }
}

如您所见,DataTableModel.refresh() 在新线程中运行。对fireTableDataChanged 的调用可能会导致eventQueue 线程重新绘制表格,进而调用getValueAt 方法。尽管modelRow 的值小于data.size(),但这里还是会抛出异常。不确定如何存在竞争条件,因为 Runnable 线程在调用 fireTableDataChanged 之后调用 reloadData(它重新填充数据对象)。因此,当 eventQueue 线程调用 getValueAt 时,data 对象应该是稳定的。另请注意,在可运行线程出现之前,刷新按钮处于禁用状态,以避免重复调用刷新。

下面是异常信息

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 3, Size: 10
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at datatablemodels.DataTableModel.getValueAt(DataTableModel.java:70)

如果我稍微更改 reloadData 代码,异常就会消失(至少还不能重现)。我的猜测是,它只是减少了异常的可能性。

protected void reloadData()
{
    ArrayList<TextMessage> messageList = jmsConnection.getMessageList();

    ArrayList<Object> tempdata = new ArrayList<Object>();

    try
    {
        for(int i=0; i<messageList.size(); ++i)
        {
            tempdata.add(new MyDataObject(messageList.get(i).getJMSMessageID()));
        }
    }
    catch(Exception e)
    {
    }

    data = tempdata ;
}

我更感兴趣的是了解这里发生了什么。我确实有其他解决方案,例如 synchronizedCopyOnWriteArrayList 等。

【问题讨论】:

  • 您是否在多线程环境中工作?
  • 并非如此。只有您在buttonClicked 中看到的new Thread(new Runnable 是我直接创建的。我没有创建长时间运行的线程。
  • @mpathi 你双击了吗?如果您双击,它可能会发生。
  • 不双击。我也对此表示怀疑。因此,我在每次点击之间用 4 到 5 秒的单次点击进行了缓慢的测试。该问题随机发生。有时在点击 3 次后,有时在点击 30 次后。
  • 您确实需要将其缩减为最低完整可验证示例stackoverflow.com/help/mcve

标签: java multithreading exception arraylist


【解决方案1】:

由于您没有同步,因此您无法保证在 refresh() 方法中所做的数据更改(在其自己的线程中运行)将在运行 getValueAt() 方法的其他线程中可见(如果有的话)。例如,getValueAt() 方法或其中的一部分可能会在 reloadData() 方法中的 data.clear() 调用和数据表的重新填充“之间”运行。

并且当您放入 println() 调用时,它们可能会在 data.clear() 调用之前或重新填充数据之后运行,即使 data.get() 调用没有。

底线是,每当数据被多个线程访问时,就像这里一样,您必须以一种或另一种方式同步对数据的访问。

【讨论】:

  • 没有显式同步。然而,getValueAt()reloadData 完成运行后被触发(使用fireTableDataChanged)。此外,我确保不能并行调用 refresh。因此,对于 data 对象的 Runnable 线程和 Event 线程之间如何存在竞争,我感到很困惑。异常消息还将索引为3,大小为10
  • 如果没有显式同步,“after”这个词对于不同线程中的事件没有意义。
  • 在这种情况下,在事件线程中调用/导致 getValueAt() 的是 Runnable 线程。它在重新填充数据对象后清楚地做到了这一点。如果 Thread1 完全执行了 Task1,然后请求 Thread2 执行 Task2,Task1 和 Task2 怎么会重叠呢?
猜你喜欢
  • 2022-01-13
  • 2020-08-18
  • 2020-09-28
  • 2021-12-10
  • 2019-03-25
  • 2020-09-03
  • 1970-01-01
  • 2017-07-30
相关资源
最近更新 更多