如何同步不可修改的集合

时间:2016-02-08 23:06:25

标签: java multithreading collections immutability

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

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

所以我编写了以下代码,不幸的是它抛出了一个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是非确定性行为。

根据我的理解在同步集合上放置一个不可修改的包装器不会有任何访问权限 必须保持正确迭代的互斥锁。

4 个答案:

答案 0 :(得分:1)

如果您可以确保集合上集合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 */
  }
}

答案 1 :(得分:1)

你需要首先决定一些事情。

一个。返回集合的用户是否应该自动查看对它的更新,以及何时?如果是这样,你需要注意不要(或决定这是否可行)意外地将其锁定以便更新一段时间。如果对返回的集合使用synchronized和synchroning,则有效地允许返回集合的用户锁定它以进行更新。

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

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

可能的解决方案:

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

答案 2 :(得分:0)

您问的是“如何同步不可修改的集合”,但实际上这不是您在代码中所做的。您使同步集合无法修改。如果您第一次使您的收藏无法修改,然后同步它,那么您将得到您想要的。

// 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);

然而,由于同样的原因,添加,删除内部仍然会失败。

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

答案 3 :(得分:0)

您可能希望使用其中一个并发集合。如果我正确地阅读了您的问题,那将为您提供所需的信息。只需接受支付费用。

List<T> myCollection = new CopyOnWriteArrayList<T>();
List<T> clientsCollection = Collections.unmodifiableList(myCollection);
通过这种方式,您将无法获得CME,因为客户端将始终获得不可修改的集合并且不会干扰您的写入。但是,价格相当高。