Java 如何克隆已同步的集合?

Java 如何克隆已同步的集合?,java,collections,synchronized,Java,Collections,Synchronized,想象一个同步的集合: Set s = Collections.synchronizedSet(new HashSet()) 克隆此收藏的最佳方法是什么 建议克隆不需要对原始集合进行任何同步,但要求对克隆集合进行迭代不需要对原始集合进行任何同步。在同步块内使用复制构造函数: synchronized (s) { Set newSet = new HashSet(s); //preferably use generics } 如果还需要同步副本,请再次使用Collections.sync

想象一个同步的
集合

Set s = Collections.synchronizedSet(new HashSet())
克隆此收藏的最佳方法是什么


建议克隆不需要对原始集合进行任何同步,但要求对克隆集合进行迭代不需要对原始集合进行任何同步。

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

synchronized (s) {
    Set newSet = new HashSet(s); //preferably use generics
}
如果还需要同步副本,请再次使用
Collections.synchronizedSet(..)

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

在对返回集进行迭代时,用户必须手动对其进行同步


通过执行以下操作可以避免同步集合,从而避免在原始集合上公开迭代器

Set newSet = new HashSet(Arrays.asList(s.toArray())); 
从集合编辑。已同步的集合

public Object[] toArray() {
    synchronized(mutex) {return c.toArray();}
}
如您所见,在执行操作的整个过程中都会保持锁定。因此,将获取数据的安全副本。迭代器是否在内部使用并不重要。返回的数组可以以线程安全的方式使用,因为只有本地线程有对它的引用


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

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

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

Set newSet = s.clone();//preferably use generics
使用集合的克隆进行快照处理并不少见。如果这正是您所追求的,那么您可以添加一些代码来处理项目已经被处理的情况。包含在多个副本集中的临时对象所涉及的开销通常小于使用
Collections.concurrentSet()
的一致开销


编辑:我刚刚注意到ConcurrentSkipListSet是可克隆的,它提供了线程安全的
clone()
方法。我更改了我的答案,因为我真的认为这是最好的选择——而不是失去
集合的可伸缩性和性能。concurrentSet()

不幸的是,这使用了引擎盖下的迭代器,所以您必须同步以安全地完成此操作。@Peter是的,显然,如果不使用迭代器,几乎没有一种方法可以访问集合。问题的坏处在于,您必须知道该方法是如何实现的。副本的条件是什么?复制时原始集是否可以更改?你想通过克隆来完成什么?(也许可以提出更有效的方法来实现你的目标,但需要这些问题的答案以确保它对你来说是正确的)@Berin Loritsch:是的,在复制过程中原始设置可能会改变。我试图从一个集合中获取一个迭代器,该集合在数据和同步的意义上应该独立于原始集合。是的,但它在副本上进行迭代。这是线程安全的。toArray()是线程安全的,因为它是原子的。我看不到任何地方可以保证
toArray()
是原子的——至少在这种情况下是如此。我看到的唯一保证是对
toArray()
的调用创建了一个数组的新副本——即使该集合由数组支持。我不想把我的代码押在那个假设上。复制阵列需要时间,尤其是当源由节点支持时(就像所有集合和LinkedList一样)。在这种情况下需要迭代器。@Berin,知道Collections.synchronizedSet()如何工作,您可以说任何单个操作都是线程安全的。迭代器()是线程安全的,但它返回的迭代器不是引用原始集合的迭代器,在使用它时,锁已被释放。toArray()是线程安全的,它返回的元素的副本只有线程持有,因此它也是线程安全的。假设它是synchronizedSet(),则您的语句是正确的。但是,我仍然认为synchronizedSet将迫使您失去一些您希望通过运行多个线程获得的并发性。使用为并发性而设计的集合将提供更细粒度的控制——但是您对toArray的假设将不再适用。重要的是,人们要知道是什么使调用原子化,而不是一直假设调用原子化。@Berin,OP说他正在使用synchronizedSet()。我不知道如何为并发性设计集合,但假设toArray()至少是线程安全的(如果不是原子的话)是不安全的。(实际上只有前者是必需的)你能举例说明这是怎么可能的吗?