【问题标题】:Problems with two threads accessing a frequently updated Arraylist两个线程访问频繁更新的 Arraylist 的问题
【发布时间】:2011-08-03 07:08:09
【问题描述】:

我有存储许多对象的 ArrayLists,并且经常在 ArrayLists 中添加和删除对象。一个线程对数据结构进行操作,每 20 毫秒左右更新一次 ArrayList 的对象。另一个线程遍历 ArrayLists 并使用它们的元素来绘制对象(也是每 20-30 毫秒)。

如果使用 for 循环遍历 ArrayList,则 IndexOutOfBoundsExceptions 比比皆是。如果使用迭代器遍历 ArrayList,则 ConcurrentModificationExceptions 比比皆是。像这样同步 ArrayLists:


List list = Collections.synchronizedList(new ArrayList());
synchronized(list) {
//use iterator for traversals
}

不抛出异常,但会消耗大量性能。有没有办法遍历这些 ArrayLists 而不会抛出异常并且不会消耗性能?

谢谢!

【问题讨论】:

  • 是的,你有线程错误,你很幸运能得到你的例外。第一个线程做了哪些修改?某些范例比其他范例更容易使线程安全。如果列表很小,可以考虑CopyOnWriteArrayList
  • 我使用了 CopyOnWriteArrayList,似乎运行良好。但是,当我使用迭代器遍历列表时,对 remove(object) 的调用会引发 UnsupportedOperationException。 for 循环通过它没有问题。
  • 如果要从遍历过程中确定的 CopyOnWriteArrayList 中删除对象,请将它们全部保存在单独的集合中,并在完成后调用 removeAll()。
  • 我怀疑您需要在“绘画”代码中对列表进行结构修改。您需要进一步解耦两个线程,否则由于线程之间的同步,您将陷入性能不佳的困境。如果您发布代码的简化版本,也许人们可以帮助您重新组织它。

标签: java multithreading data-structures iterator arraylist


【解决方案1】:

虽然 CopyOnWriteArrayList 为读取器提供了最高性能,但如果写入频繁,则会出现写入器性能问题。

如果您的访问模式仅通过迭代器并且您不进行任何随机访问,那么使用队列可能是更好的选择,因为您可以使用 ConcurrentLinkedQueue 之类的东西。参见例如JAVA: Concurrency control for access to list in java

【讨论】:

    【解决方案2】:

    您是否尝试过使用IteratorCopyOnWriteArrayList?保证不抛出ConcurrentModificationExceptions。

    来自 Oracle javadocs(已添加重点):

    ArrayList 的线程安全变体 其中所有变异操作(添加, 集,等等)由实现 制作底层证券的新副本 数组。

    这通常成本太高,但可能 比替代品更有效 当遍历操作很大时 多于突变,并且很有用 当你不能或不想 同步遍历,但需要 排除并发之间的干扰 线程。 “快照”样式迭代器 方法使用对状态的引用 在该点的数组 创建了迭代器。这个数组从不 在生命周期的变化 迭代器,所以干扰是 不可能,迭代器是 保证不扔 并发修改异常。这 迭代器不会反映添加, 自删除或更改列表以来 创建了迭代器。 元素改变操作 迭代器本身(删除、设置和 不支持添加)。这些方法 抛出 UnsupportedOperationException。

    【讨论】:

      【解决方案3】:

      解决这个问题的一个好方法是让线程在列表的不同副本上工作。但是,CopyOnWriteArrayList 不太适合这里,因为它会在每次修改时创建一个副本,但在您的情况下,最好不要频繁地创建副本。

      因此,您可以手动实现它:第一个线程创建更新列表的副本并通过 volatile 变量发布它,第二个线程使用此副本(我假设第一个线程只修改列表,而不是对象):

      private volatile List publicList;
      
      // Thread A
      List originalList = ...;
      while (true) {
          modifyList(originalList); // Modify list
          publicList = new ArrayList(originalList); // Pusblish a copy
      }
      
      // Thread B
      while (true) {
          for (Object o: publicList) { // Iterate over a published copy
              ...
          }
      }
      

      【讨论】:

      • +1 一个不错的缓存策略,它比CopyOnWriteArrayList 更精细。正在更改数组的线程在“工作”副本上工作,并定期更新“快照”副本。仅呈现列表内容的线程适用于“快照”副本。
      • 我把你的换成了最佳答案。 CopyOnWriteArrayLists 是性能猪。
      【解决方案4】:

      在迭代之前将 ArrayList 复制到一个新变量中怎么样?这样你只需要同步复制块而不是列表的整个迭代。

      【讨论】:

        【解决方案5】:

        您可以使用 CopyOnWriteArrayList ,它不会获得 ConcurrentModificationException 也不需要同步,或者您可以执行类似的操作。

        List list = Collections.synchronizedList(new ArrayList());
        
        List copy;
        // lock the list for the minimal amount of time.
        synchronized(list) {
            copy = new ArrayList(list);
        }
        // use the copy of the array list.
        

        BTW CopyOnWriteArrayList 看起来像

        List list = new CopyOnWriteArrayList();
        
        // use the list.
        

        【讨论】:

          【解决方案6】:

          CopyOnWriteArrayList 类是为了解决这个问题而创建的。

          【讨论】:

            猜你喜欢
            • 2013-08-09
            • 2013-10-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-11-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多