在使用Collections.synchronizedX方法后调用Java 8集合默认方法是否安全?

3
Java 8在集合框架中的接口上添加了许多默认方法,但是Collections.synchronizedX方法的JavaDoc几乎没有改变。我不确定在它们返回的结果上调用新的默认方法是否安全?我检查了Oracle JDK源代码,它们似乎被重写为线程安全,但是所有JDK都有保证吗?

4
他们没有更改Javadoc,它保证了所有方法都是通过同步实现线程安全的。如果他们没有将这种行为传播到默认方法中,那就是一个bug。 - assylias
@assylias 然而,如果你从一个第三方的 JDK 8 之前的库的角度来看,那么在 JacaDoc 中没有任何变化,但是它所有的集合包装器都不再是线程安全的。然而,我想这也意味着这个问题不能超出 JDK 的范围得到现实的回答。 - billc.cn
1
@billc.cn:这就是为什么你不应该在这种任务中使用第三方库。然而,由于“default”方法的存在,这种类的线程安全性并没有改变。它仍然像以前一样安全,只是复合操作需要额外的努力,这也和以前一样。 - Holger
@Holger,有一些库(例如Guava)与JDK一样高质量且经过充分测试。不幸的是,它们也会受到语言中未预料到的变化的影响,尽管JavaDoc提供的契约与JDK相同。示例 - billc.cn
1
这样的更改不会破坏现有代码,因为现有代码不知道新的“default”方法。另一方面,新代码可以切换到Collections.synchronizedNavigableMap。这与质量无关,只是因为接口和特定实现是同一个库的一部分,因此保持同步。 - Holger
2
除此之外,我从来不喜欢这些synchronized...集合包装器,因为现实操作几乎总是复合的,并且需要显式锁定。因此,一开始就使用显式锁定不会给人错误的印象,即线程安全就像同步每个方法一样简单。它避免了像臭名昭著的检查-然后-执行反模式这样的错误。 - Holger
1个回答

3
在OpenJDK/OracleJDK中,事实情况如下:
  • 新的spliterator()stream()parallelStream()方法没有同步,并且必须在外部手动同步(类似于之前存在的iterator()listIterator())。

  • 其他新方法是同步的,包括forEachremoveIfreplaceAllsortgetOrDefaultputIfAbsentreplacecomputeIfAbsentcomputeIfPresentcomputemerge

这种行为实际上是有规定的:

当通过IteratorSpliteratorStream遍历返回的集合时,用户必须手动对其进行同步。

因此,您可以期望除了这些显式提到的异常之外的任何其他方法都是同步的。微妙的问题在于它仅针对synchronizedCollection进行了说明,但并没有针对其他方法进行明确说明,并且没有明确说明例如synchronizedListsynchronizedCollection继承了一些行为(虽然实际上确实如此)。

请注意,像forEachreplaceAll这样的大量处理方法在整个迭代期间都持有锁定器,因此您最终有机会安全地迭代/更新整个集合。但是,您应该注意可能发生死锁/饥饿的情况,因为集合可能会被长时间锁定。

还要注意,当前状态引入了syncCollection.forEach(...)syncCollection.stream().forEach(...)之间的差异:第二个调用未同步。

更新:我向OpenJDK开发人员报告了规范化synchronizedXXX方法必须更新的事实,并提交了一个补丁,已经被JDK-9接受。


1
是的,更好的是,由于Collections.sort委托给List.sort,将其应用于synchronizedList,您可以在原始列表的范围内获得线程安全性和潜在的高性能执行,例如当后备列表是ArrayList时... - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接