【问题标题】:How to clone a synchronized Collection?如何克隆一个同步的集合?
【发布时间】:2011-06-02 20:20:46
【问题描述】:

想象一个同步的 Collection:

Set s = Collections.synchronizedSet(new HashSet())

克隆此集合的最佳方法是什么?

希望克隆不需要对原始 Collection 进行任何同步,但要求对克隆的 Collection 进行迭代不需要对原始 Collection 进行任何同步。

【问题讨论】:

  • 复制的条件是什么?复制时可以更改原始设置吗?你想通过克隆来完成什么? (也许能够提出更有效的方法来实现您的目标,但需要这些问题的答案以确保它对您是正确的)
  • @Berin Loritsch:是的,原始集可以在复制期间更改。我正在尝试从集合中获取一个迭代器,该集合在数据和同步方面应该独立于原始集合。

标签: java collections synchronized


【解决方案1】:

在同步块中使用复制构造函数:

synchronized (s) {
    Set newSet = new HashSet(s); //preferably use generics
}

如果您还需要同步副本,请再次使用Collections.synchronizedSet(..)

根据 Peter 的评论 - 您需要在原始集的同步块中执行此操作。 synchronizedSet 的文档对此有明确的说明:

用户在迭代返回的集合时必须手动同步它

【讨论】:

  • 不幸的是,这使用了引擎盖下的迭代器,因此您必须同步才能安全地执行此操作。
  • @Peter 是的,显然,几乎没有办法在不使用迭代器的情况下访问集合
  • 不好的一面是你必须知道方法是如何实现的。
【解决方案2】:

使用同步集合时,请务必了解访问集合中的每个元素会产生同步开销。 Collections.synchronizedSet() 只是用一个 shell 包装你的集合,强制每个方法同步。可能不是你真正想要的。 ConcurrentSkipListSet 将在多个线程将写入集合的多线程环境中为您提供更好的性能。

ConcurrentSkipListSet 将允许您执行以下操作:

Set newSet = s.clone();//preferably use generics

使用集合的克隆进行快照处理的情况并不少见。如果这就是您所追求的,您可能会添加一些代码来处理已处理项目的情况。包含在多个副本集中的偶尔对象所涉及的开销通常小于使用Collections.concurrentSet() 的一致开销。

编辑:我刚刚注意到 ConcurrentSkipListSet 是可克隆的,并提供了一个线程安全的 clone() 方法。我改变了我的答案,因为我真的相信这是最好的选择——而不是失去可扩展性和性能到Collections.concurrentSet()

【讨论】:

    【解决方案3】:

    您可以通过执行以下操作避免在原始集合上暴露迭代器来避免同步集合。

    Set newSet = new HashSet(Arrays.asList(s.toArray())); 
    

    从 Collections.SynchronizedCollection 编辑

    public Object[] toArray() {
        synchronized(mutex) {return c.toArray();}
    }
    

    如您所见,锁在操作执行的整个过程中都被持有。因此,会获取数据的安全副本。是否在内部使用 Iterator 并不重要。返回的数组可以以线程安全的方式使用,因为只有本地线程对它有引用。

    注意:如果您想避免这些问题,我建议您使用 2004 年 Java 5.0 中添加的并发库中的 Set。我还建议您使用泛型,因为这可以使您的集合更安全。

    【讨论】:

    • 是的,但它会遍历副本。这是线程安全的。 toArray() 是线程安全的,因为它是原子的。
    • 我没有看到任何地方可以保证toArray() 是原子的——至少在这种情况下很重要。我看到的唯一保证是对toArray() 的调用会创建一个数组的新副本——即使该集合是由一个数组支持的。我不想把我的代码放在那个假设上。复制数组需要时间,尤其是在源由节点支持的情况下(所有集合和 LinkedList 都是这种情况)。在这种情况下,将需要一个迭代器。
    • @Berin,了解 Collections.synchronizedSet() 的工作原理后,您可以说任何单个操作都是线程安全的。 iterator() 是线程安全的,但它返回的 Iterator 不是因为它引用原始集合,并且当您使用它时,锁已被释放。 toArray() 是线程安全的,它返回只有线程持有的元素的副本,因此它也是线程安全的。
    • 假设它是一个 synchronizedSet(),你的说法是正确的。但是,我仍然认为 synchronizedSet 将迫使您失去一些您可能希望通过运行多个线程获得的并发性。使用为并发设计的集合将提供更细粒度的控制——但您对 toArray 的假设将不再适用。重要的是人们知道是什么使调用原子化,而不是假设它一直都是。
    • @Berin,OP 声明他正在使用 synchronizedSet()。我看不出如何为并发设计集合,但假设 toArray() 至少是线程安全的,即使不是原子的,也是不安全的。 (实际上只需要前者)你能举个例子说明这是怎么可能的吗?
    猜你喜欢
    • 2014-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2013-03-28
    相关资源
    最近更新 更多