繁体   English   中英

如何同步不可修改的集合

[英]How to synchronize unmodifiable collections

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

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

所以我写了下面的代码,不幸的是它抛出了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中所述,惩罚是不确定的行为。

根据我的理解,在同步的集合上放置一个不可修改的包装将无法访问为正确迭代而必须持有的互斥量。

如果你能保证只读集合的客户端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 */
  }
}

您需要先决定几件事。

答:返回的集合的用户是否应该在何时看到它的更新? 如果是这样,您将需要注意不要(或确定是否可以)无意地将其锁定一段时间以进行更新。 如果在返回的集合上使用同步和同步,则可以有效地允许返回的集合的用户锁定它,例如进行更新。

B.还是需要再次致电以获取新的收藏?

此外,使用Collections.synchronizedX不会给您提供任何保护,以防止对其进行迭代,而只是对它们进行单独的读取和写入。 因此,将要求客户端保证它在所有显式和隐式迭代期间均锁定。 听起来总体上不好,但是我想这取决于。

可能的解决方案:

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

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

您在问“如何同步不可修改的集合”,但实际上这不是您在代码中所做的。 您已将同步化的集合设为不可修改。 如果您1st使您的收藏无法修改,然后使其同步,那么您将得到想要的东西。

// 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内添加,删除仍然会失败。

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

您可能要使用并发集合之一。 如果我正确阅读了您的问题,那将给您您所需要的。 只接受支付费用。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM