【问题标题】:How to synchronize unmodifiable collections如何同步不可修改的集合
【发布时间】:2016-05-18 18:56:33
【问题描述】:

我想将类的不可修改视图(维护项目集合)返回给外部客户端。

所以为了保护并发访问,我需要先将集合包装在一个同步包装器中,然后在我返回到外部线程的版本周围放置一个不可修改的包装器。

所以我编写了以下代码,不幸的是它抛出了 ConcurrentModificationException。 .

import java.util.*;

public class Test {
public static void main(String[] args) {
    // assume c1 is private, nicely encapsulated in some class
    final Collection col1 = Collections.synchronizedCollection(new ArrayList());
    // this unmodifiable version is public
    final Collection unmodcol1 = Collections.unmodifiableCollection(col1);

    col1.add("a");
    col1.add("b");

    new Thread(new Runnable() {
        public void run() {
            while (true) {
                // no way to synchronize on c1!
                for (Iterator it = unmodcol1 .iterator(); it.hasNext(); it.next())
                    ;
            }
        }
    }).start();

    while (true) {
        col1 .add("c");
        col1 .remove("c");
    }
   }
 }

所以我的问题是如何同步不可修改的集合?

添加更多

当收到集合的客户端想要迭代其元素时

1) 它不一定知道它是一个同步的集合和

2) 即使这样做,它也无法在同步包装互斥锁上正确同步以迭代其元素。处罚,如中所述 Collections.synchronizedCollection,是不确定的行为。

据我了解 在同步集合上放置不可修改的包装器会导致无法访问 到必须保持正确迭代的互斥体。

【问题讨论】:

  • 看看你能不能只返回一个副本/快照。让事情变得更容易
  • @ShowStopper,我不明白你在问题中添加了什么。 2.客户端不用同步,JVM和Iterator替他做。
  • Collections.java:2030 public Iterator iterator() { return c.iterator(); // 必须由用户手动同步! }。如您所见,您的尝试不会阻止 CME,因为迭代器未同步。如果你想让迭代器同步,你可能需要自己包装它。但是,该迭代器将是不确定的,因为在 next() 之前添加元素意味着客户端不会触摸它。
  • 您不需要同步不可修改的集合,因为它是不可修改的。在您的情况下,该集合是可修改的,因此您需要使用一个集合,即使它正在被修改,您也可以对其进行迭代,例如CopyOnWriteArrayList

标签: java multithreading collections immutability


【解决方案1】:

您可能想要使用并发集合之一。如果我正确阅读了您的问题,那将为您提供所需的东西。只需接受按成本付费即可。

List<T> myCollection = new CopyOnWriteArrayList<T>();
List<T> clientsCollection = Collections.unmodifiableList(myCollection);

这样,您将不会获得 CME,因为客户端将始终获得不可修改的集合,并且不会干扰您的写入。不过价格偏高。

【讨论】:

    【解决方案2】:

    你需要先决定几件事。

    A.返回集合的用户是否应该自动看到它的更新,何时?如果是这样,您需要注意不要(或决定是否可以)意外锁定它以进行一段时间的更新。例如,如果在返回的集合上使用同步和同步,您实际上是允许返回集合的用户锁定它以进行更新。

    B.或者他们是否需要再次打电话来获得新的收藏?

    此外,使用 Collections.synchronizedX 不会为您提供任何防止迭代它的保护,只是单独的读取和写入。因此需要客户端保证它在所有显式和隐式迭代期间锁定。总的来说听起来很糟糕,但我猜这取决于。

    可能的解决方案:

    1. 返回一个副本,甚至不需要将其包装成不可修改的。只需在创建时锁定它。 synchronized (collection) { return new ArrayList(collection); } 无需进一步同步。上述选项 B 的示例实现。
    2. 与 1 类似,但由数据结构本身自动使用 CopyOnWriteArrayList 并返回它(以不可修改的形式包装)。注意:这意味着对集合的写入是昂贵的。读取不是。另一方面,即使对其进行迭代也是线程安全的。不需要任何同步。支持上述选项 A。
    3. 根据您需要的数据结构的属性,您可以选择非 RandomAccess 列表,如 ConcurrentLinkedQueueConcurrentLinkedDeque,两者都允许在数据结构上进行迭代等,而无需任何额外的同步。再次,包裹在不可修改中。支持上述选项 A。

    对于一般情况,我会选择选项 B-1 并开始使用。但这取决于往常。

    【讨论】:

      【解决方案3】:

      如果您可以确保集合上的集合synchronize 的只读客户端,请在您的生产者中的同一视图上同步:

      /* In the producer... */
      Collection<Object> collection = new ArrayList<>();
      Collection<Object> tmp = Collections.unmodifiableCollection(collection);
      Collection<Object> view = Collections.synchronizedCollection(tmp);
      synchronized (view) {
        collection.add("a");
        collection.add("b");
      }
      /* Give clients access only to "view" ... */
      
      /* Meanwhile, in the client: */
      synchronized (view) {
        for (Object o : view) {
          /* Do something with o */
        }
      }
      

      【讨论】:

        【解决方案4】:

        您问的是“如何同步不可修改的集合”,但实际上这不是您在代码中所做的。您使同步集合不可修改。如果您首先使您的收藏不可修改,然后对其进行同步,那么您将得到您想要的。

        // you'll need to create the list
        final ArrayList list = new ArrayList();
        // and add items to it while it's still modifiable:
        list.add("a");
        list.add("b");
        final Collection unmodcol1 = Collections.unmodifiableCollection(list);
        
        final Collection col1 = Collections.synchronizedCollection(unmodcol1);
        

        但是,由于同样的原因,while 内的添加、删除仍然会失败。

        另一方面,如果您创建了列表并使其不可修改,那么您可能根本不需要同步它。

        【讨论】:

        • 不......请在回答之前运行代码我现在得到以下异常。线程“主”java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055) at java.util.Collections$SynchronizedCollection.add(Collections.java:2035) at Test.main( Test.java:10)
        • 没错。那为什么呢?因为您正在尝试通过添加来修改不可修改的列表
        • 这就是我先同步的原因。
        • 没关系,因为你让它不可修改后还想添加它。立即查看我的编辑
        猜你喜欢
        • 2012-02-12
        • 2015-09-07
        • 2011-02-22
        • 1970-01-01
        • 1970-01-01
        • 2013-11-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多