通过Collections.synchronizedSet(...)。forEach()的迭代是否保证是线程安全的?

时间:2014-05-03 21:55:39

标签: java collections thread-safety java-8

我们知道,默认情况下迭代并发集合不是线程安全的,所以不能使用:

Set<E> set = Collections.synchronizedSet(new HashSet<>());
//fill with data
for (E e : set) {
    process(e);
}

这是因为在迭代期间可能会添加数据,因为set上没有排他锁。

这在Collections.synchronizedSet的{​​{3}}中描述:

  

public static Set synchronizedSet(Set s)

     

返回由指定集支持的同步(线程安全)集。为了保证串行访问,必须通过返回的集合完成对后备集的所有访问。

     

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

     

Set s = Collections.synchronizedSet(new HashSet());
        ...
     synchronized (s) { Iterator i = s.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }

     

不遵循此建议可能会导致非确定性行为。

,这不适用于从javadoc继承默认方法Set.forEach的{​​{1}}。

现在我查看了源代码,在这里我们可以看到我们有以下结构:

  1. 我们要求forEach
  2. 我们得到一个:

    Collections.synchronizedSet()
  3. 它扩展了public static <T> Set<T> synchronizedSet(Set<T> s) { return new SynchronizedSet<>(s); } ... static class SynchronizedSet<E> extends SynchronizedCollection<E> implements Set<E> { private static final long serialVersionUID = 487447009682186044L; SynchronizedSet(Set<E> s) { super(s); } SynchronizedSet(Set<E> s, Object mutex) { super(s, mutex); } public boolean equals(Object o) { if (this == o) return true; synchronized (mutex) {return c.equals(o);} } public int hashCode() { synchronized (mutex) {return c.hashCode();} } } ,在明显的方法旁边有以下有趣的方法:

    SynchronizedCollection
  4. 此处使用的// Override default methods in Collection @Override public void forEach(Consumer<? super E> consumer) { synchronized (mutex) {c.forEach(consumer);} } @Override public boolean removeIf(Predicate<? super E> filter) { synchronized (mutex) {return c.removeIf(filter);} } @Override public Spliterator<E> spliterator() { return c.spliterator(); // Must be manually synched by user! } @Override public Stream<E> stream() { return c.stream(); // Must be manually synched by user! } @Override public Stream<E> parallelStream() { return c.parallelStream(); // Must be manually synched by user! } mutex锁定的所有操作的对象相同。

    现在我们可以通过实现来判断使用Collections.synchronizedSet是否可以线程安全,但它是否也是规范的线程安全

    (令人困惑的是,Collections.synchronizedSet(...).forEach(...) 线程安全的实现,并且规范的判定似乎也是未知的。)

3 个答案:

答案 0 :(得分:11)

正如您所写的那样,根据实施情况来看,forEach()对于JDK提供的集合是线程安全的(请参阅下面的免责声明),因为它需要监视要获取的互斥锁才能继续。

  

规范是否也是线程安全的?

我的意见 - 不,这是一个解释。用短语重写的Collections.synchronizedXXX() javadoc说 - “除了那些用于迭代它的方法之外,所有方法都是线程安全的。”

我的另一个,虽然非常主观的论点是 yshavit 所写的 - 除非告知/读取,考虑API /类/任何不是线程安全的。

现在,让我们仔细看看javadoc。我想我可能会声明方法forEach()用于迭代它,因此,遵循javadoc的建议,我们应该认为它不是线程安全的,尽管它与现实(实现)相反。

无论如何,我同意 yshavit 的声明,即文档应该更新,因为这很可能是文档,而不是实现缺陷。但是,除了JDK开发人员之外,没有人可以肯定地说,请看下面的问题。

我想在本次讨论中提到的最后一点 - 我们可以假设自定义集合可以用Collections.synchronizedXXX()包装,并且此集合的forEach()的实现可以是... can做任何事。该集合可能会对forEach()方法中的元素执行异步处理,为每个元素生成一个线程......它仅受作者想象力的限制,并且 synchronized(互斥)包裹不能保证此类线程的安全性。例即可。该特定问题可能是不将forEach()方法声明为线程安全的原因..

答案 1 :(得分:6)

值得看看documentation of Collections.synchronizedCollection而不是Collections.synchronizedSet()作为已经清理文档:

  

用户在遍历它时必须手动同步返回的集合   通过IteratorSpliteratorStream:...

我认为,这很明显,通过除了同步Collection本身之外的对象和使用其forEach方法的迭代之间存在区别。但即使使用旧的措辞,您也可以得出结论:存在这样的区别:

  

当迭代它时,用户必须手动同步返回的集合 :...

(我强调)

documentation for Iterable.forEach比较:

  

Iterable的每个元素执行给定操作,直到处理完所有元素或操作引发异常。

虽然开发人员很清楚必须进行(内部)迭代才能实现这一点,但这次迭代是一个实现细节。根据给定规范的措辞,它只是一个(元)动作,用于对每个元素执行操作。

使用该方法时,用户 迭代元素,因此不对Collections.synchronized…文档中提到的同步负责。

然而,这有点微妙,documentation of synchronizedCollection明确列出手动同步的情况是好的,我认为其他方法的文档也应该适应。

答案 2 :(得分:0)

如@Holger所说,doc clearly says用户在通过Collections.synchronizedXyz()遍历Iterator时必须手动同步Collection c = Collections.synchronizedCollection(myCollection); ... synchronized (c) { Iterator i = c.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); } 返回的集合:

当通过Iterator,Spliterator或Stream遍历返回的集合时,用户必须手动对其进行同步:

public Iterator<E> iterator() {
    return c.iterator(); // Must be manually synched by user!
}

我想进一步解释一下代码。

考虑Collections.synchronizedList() method。它返回Collections.SynchronizedList类实例,该实例扩展了SynchronizedCollection,它定义了iterator(),如下所示:

SynchronizedCollections

将此方法与public String toString() { synchronized (mutex) {return c.toString();} } 的其他方法进行比较,例如:

SynchronizedList

因此iterator()SynchronizedCollection继承了render,需要用户手动对其进行同步。