【问题标题】:Why is arraylist's size not right when multiple threads add elements into it?为什么当多个线程向其中添加元素时,arraylist 的大小不正确?
【发布时间】:2016-10-26 09:01:28
【问题描述】:

为什么Arraylist在多线程添加元素时大小不合适?

threadCount = 100;
List<Object> list = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
    Thread thread = new Thread(new MyThread(list, countDownLatch));
    thread.start();
}

class MyThread implements Runnable {
    // ......

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            list.add(new Object());
        }
    }
}

这个程序完成后,列表的大小应该是 10000。实际上,大小可能是 9950、9965 或其他一些数字。为什么?

我知道为什么这个程序可能会引发IndexOutofBoundsException 以及为什么其中有一些空值,但我就是不明白为什么大小不对。

【问题讨论】:

  • ArrayList 不是线程安全的。
  • 来自文档:“请注意,此实现不同步。如果多个线程同时访问 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则必须在外部同步。”你没有这样做。
  • @fei_hsueh 文档基本上说该行为是未指定的 - 所以它可能有效也可能无效......
  • @fei_hsueh 问题是问为什么尺寸 is 不对,而不是为什么尺寸没有错。
  • “我知道。” 那是谎言。如果你真的知道它,那么这个问题就不会在这里。只需谷歌“比赛条件”。可能的骗子:stackoverflow.com/questions/3836680/…stackoverflow.com/questions/34510/what-is-a-race-condition

标签: java arraylist concurrency


【解决方案1】:

我就是不明白为什么尺寸不对?

由于 ArrayList 不是线程安全的,因此两个线程可能会在同一个索引处相加。所以一个线程会覆盖。

【讨论】:

  • 两个线程在同一个索引处相加,但大小仍然 +1 两次。最后,尺寸要合适。这只是将一些空值添加到列表中。
  • 检查 javadoc 的 add 方法。它清楚地表明最后将添加元素。现在两个线程发现'n'是最后一个索引。并且都在相同的索引处添加对象。并且大小仅增加 1。
  • “但大小仍然 +1 两倍”只是不是原子的。两个线程的 size 获取和 size 新值的存储可以交错进行。比如两个线程可以同时读取size == 0;然后两者都尝试存储size = 1
  • @Andy Turner: 并且检查结果的线程甚至可能在其他线程将其设置为 1 之前读取了 size 字段,在这种情况下,它会认为它是 0
【解决方案2】:

当您在主 ui 线程中的任何位置使用 arraylist.clear() 时,如果在后台服务上完成数组列表操作,最后如果在执行后台操作后在主 ui 线程中有数组列表,则数组列表列表的大小为零。

        //don't use it in main ui thread 
        activity?.runOnUiThread {
               // it comes myarraylist size is 0 at end
                myarraylist.clear()
            }
    ....
    
     //some background opertion add to arraylist
....
    activity?.runOnUiThread {
         //if you set adapter it may get 0 size
               
            }

【讨论】:

    【解决方案3】:

    arraylist不是线程安全的,所以如果对一个arraylist对象进行多线程操作,结果是undefined,这意味着任何事情都可能因为jvm而发生,包括但不限于大小错误, 异常, 内存泄漏, 不出所料

    【讨论】:

      【解决方案4】:

      很简单, 您正在对对象执行不安全的操作... 所以你无法预测稳定性... 如果您运行了足够多的时间,则可能有一个实例 list.size() 将返回 10000。 为什么它总是不回来.???。答案是,因为它不是线程安全的。现在考虑在arraylist中添加的代码

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

      这里假设当线程 10 在“ensureCapacity”内并且它进入睡眠状态时,其他线程(比如线程 11)将会到来并且它们(线程 10 和 11)最终将在一个位置插入 2 个元素。这是发生在这里

      【讨论】:

        【解决方案5】:

        您应该参考 ArrayList 中的源代码。

        @Override public boolean add(E object) {
                Object[] a = array;
                int s = size;
                if (s == a.length) {
                    Object[] newArray = new Object[s +
                            (s < (MIN_CAPACITY_INCREMENT / 2) ?
                             MIN_CAPACITY_INCREMENT : s >> 1)];
                    System.arraycopy(a, 0, newArray, 0, s);
                    array = a = newArray;
                }
                a[s] = object;
                size = s + 1;
                modCount++;
                return true;
            }
        

        ArrayList 使用System.arraycopy(a, 0, newArray, 0, s) 插入元素。如果多线程运行,则包含元素的字段数组不是原来的,大小小于预期。

        【讨论】:

          猜你喜欢
          • 2016-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-11-16
          相关资源
          最近更新 更多