【问题标题】:Best way to tranverse List in Java?在Java中遍历List的最佳方法?
【发布时间】:2013-02-26 14:09:45
【问题描述】:

我有一个在多线程环境中使用的并发列表。一旦建立了列表,大部分操作就是遍历它。我想知道以下两种方法中哪一种更有效,或者创建新列表与使用同步的成本是多少?或者也许还有其他更好的方法?

List<Object> list = new CopyOnWriteArrayList<Object>();

public int[] getAllValue1() {
    List<Object> list2 = new ArrayList<Object>(list);
    int[] data = new int[list2.size()];
    int i = 0;
    for (Object obj : list2) {
        data[i++] = obj.getValue();
    }
    return data;
}

public int[] getAllValue2() {
    synchronized (list) {
        int[] data = new int[list.size()];
        int i = 0;
        for (Object obj : list) {
            data[i++] = obj.getValue();
        }
        return data;
    }
}

更新 getAllValue1():它是线程安全的,因为它拍摄了 CopyOnWriteList 的快照,而 CopyOnWriteList 本身就是线程安全的 List。然而,正如 sharakan 指出的那样,成本是迭代 2 个列表,并创建一个本地对象 ArrayList,如果原始列表很大,这可能会很昂贵。

getAllValue2():在同步块中也是线程安全的。 (假设其他函数正确同步。)将它放在同步块中的原因是因为我想预先分配数组,以确保 .size() 调用与迭代同步。 (迭代部分是线程安全的,因为它使用 CopyOnWriteList。)但是这里的成本是使用同步块的机会成本。如果有 100 万个客户端调用 getAllValue2(),每个客户端都必须等待。

所以我想答案真的取决于有多少并发用户需要读取数据。如果并发用户不多,可能方法2更好。否则,方法1更好。同意吗?

在我的使用中,我有几个并发客户端,可能首选方法 2。 (顺便说一句,我的列表大小约为 10k)。

【问题讨论】:

  • 但是有人写在名单上吗?此外,Object 没有 getValue() 方法。这不是您实际运行的代码。给我们更多:)
  • 如何填充列表?填充列表的代码是否同步?
  • 我跳过了初始化部分。一旦 List 被初始化,它就会留在内存中。有时,列表中会增加一些内容。
  • 对象是大值类,包含很多字段,就像数据库中一张很宽的表。对于某些用户,他们希望从表中获取整数列,这就是我在这里尝试做的。由于它必须是线程安全的,所以我使用了 CopyOnWriteList。但是在遍历列表并返回 int[] 时,我仍然需要付出额外的努力使其成为线程安全的。我认为这两个函数都是线程安全遍历,如果我在这里错了,请纠正我。
  • @gd1 我相信您所指的 getAllValue1() 中的行会以线程安全的方式始终如一地发生,因为他使用的是CopyOnWriteArrayList

标签: java performance synchronization


【解决方案1】:

getAllValue1 看起来不错,因为您需要根据对象的 a 字段返回原始类型数组。这将是两次迭代,但一致,您不会在阅读器线程之间引起任何争用。您尚未发布任何分析结果,但除非您的列表很大,否则我更担心多线程环境中的争用,而不是两次完整迭代的成本。

如果您更改 API,您可以删除一次迭代。最简单的方法是返回一个 Collection,如下所示:

public Collection<Integer> getAllValue1() {
    List<Integer> list2 = new ArrayList<Integer>(list.size());
    for (Object obj : list2) {
        list2.add(obj.getValue());
    }
    return list2;
}

如果您可以通过这种方式更改您的 API,那将是一种改进。

【讨论】:

    【解决方案2】:

    我认为第二个更有效。原因是,在第一个中,您创建另一个列表作为本地创建。这意味着如果原始列表包含大量数据,它将复制所有数据。如果它包含数百万个数据,那么这将是一个问题。

    不过有list.toArray()方法

    Collections 界面也包含一些有用的东西

      Collection synchronizedCollection = Collections.synchronizedCollection(list);
    

    List synchronizedList = Collections.synchronizedList(list);
    

    如果您需要对象 VALUE,而不是对象,请使用您的第二个代码。否则,您可以将第二个代码的适当部分替换为上述函数,然后做任何您想做的事情。

    【讨论】:

      【解决方案3】:

      编辑(再次): 由于您在写入数组列表上使用了副本(应该更加细心),因此我将获取迭代器并使用它来初始化您的数组。由于迭代器是您请求时数组的快照,因此您可以在列表上同步以获取大小,然后进行迭代,而不必担心ConcurrentModificationException 或迭代器发生变化。

      public int[] getAllValue1() {
          synchronized(list){
              int[] data = new int[list.size()];            
          }
          Iterator i = list.iterator();
          while(i.hasNext()){
              data[i++] = i.next().getValue();
          }
          return data;
      }
      

      【讨论】:

      • CopyOnWriteList 是线程安全的 List,所以 Iteration 是线程安全的。但是,我需要分配数组的大小,这需要与 getValue2() 中的迭代同步。
      猜你喜欢
      • 2018-03-09
      • 1970-01-01
      • 2010-10-04
      • 2012-05-16
      • 1970-01-01
      • 2018-11-01
      • 2016-11-30
      • 2012-03-04
      • 1970-01-01
      相关资源
      最近更新 更多