是迭代通过Collections.synchronizedSet(...)。forEach()保证线程安全? [英] Is iteration via Collections.synchronizedSet(...).forEach() guaranteed to be thread safe?

查看:680
本文介绍了是迭代通过Collections.synchronizedSet(...)。forEach()保证线程安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  Set< E> set = Collections.synchronizedSet(new HashSet<>()); 
//用数据填充
(E e:set){
process(e);
}

这是因为在迭代过程中可能会添加数据, 设置。



javadoc Collections.synchronizedSet


public static设置synchronizedSet(Set s)



-safe)集由指定集支持。为了保证串行访问,至关重要的是通过返回的集来完成对后台集的所有访问。



用户手动同步返回的在迭代时设置:



设置s = Collections.synchronizedSet(new HashSet());

...

同步{
迭代器i = s.iterator(); //必须在同步块中
while(i.hasNext())
foo(i.next());
}



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


不适用于 Set.forEach ,它继承了 forEach /Iterable.html#forEach-java.util.function.Consumer-\">每个



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


  1. 我们要求一个 Collections.synchronizedSet c $ c>。

  2. 我们得到一个:

      public static< ; T>设置< T> synchronizedSet(Set< T> s){
    return new SynchronizedSet(s);
    }

    ...

    静态类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
    }
    }


  3. SynchronizedCollection ,在显而易见的方法旁边有以下有趣的方法:

      //覆盖集合中的默认方法
    @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(); //必须由用户手动同步!
    }
    @Override
    public Stream< E> stream(){
    return c.stream(); //必须由用户手动同步!
    }
    @Override
    public Stream< E> parallelStream(){
    return c.parallelStream(); //必须由用户手动同步!
    }


$ c> mutex 与 Collections.synchronizedSet 锁定的所有操作是相同的对象。 / p>

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



Collections.synchronizedSet(...)。stream()。forEach(...)不是线程安全的执行, )

解决方案

正如你写的,通过实现判断, forEach ()对于JDK 提供的集合是线程安全的(请参阅下面的免责声明),因为它需要获取互斥体的监控才能继续。


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


,这里是一个解释。 Collections.synchronizedXXX() javadoc,简写为所有方法都是线程安全的,除了用于迭代的线程。



我的另一个,虽然非常主观的参数是什么yshavit 写 - 除非被告知/读取,考虑API /类/不是线程安全的。



现在,让我们仔细看看javadocs。我想我可以说,方法 forEach()用于迭代它,所以,根据javadoc的意见,我们应该考虑它不线程安全,虽然它是与实际情况相反。



无论如何,我同意yshavit 的声明,文档应该更新,因为这很可能是一个文档,而不是实现缺陷。但是,除了JDK开发人员,没有人能肯定地说,请参阅下面的疑虑。



在本讨论中我想提到的最后一点 - 我们可以假设custom集合可以用 Collections.synchronizedXXX()包装,并且此集合的 forEach()的实现可以。 ..可以是任何东西。该集合可以对 forEach()方法中的元素执行异步处理,为每个元素生成一个线程...它仅由作者的想象力限定,并且同步(互斥量)不能保证此类情况的线程安全。这个特定的问题可能是不将 forEach()方法声明为线程安全的原因。


As we know, iterating over a concurrent collection is not thread safe by default, so one cannot use:

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

This happens as data may be added during iteration, because there is no exclusive lock on set.

This is describe in the javadoc of Collections.synchronizedSet:

public static Set synchronizedSet(Set s)

Returns a synchronized (thread-safe) set backed by the specified set. In order to guarantee serial access, it is critical that all access to the backing set is accomplished through the returned set.

It is imperative that the user manually synchronize on the returned set when iterating over it:

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

Failure to follow this advice may result in non-deterministic behavior.

However, this does not apply to Set.forEach, which inherits the default method forEach from Iterable.forEach.

Now I looked into the source code, and here we can see that we have the following structure:

  1. We ask for a Collections.synchronizedSet().
  2. We get one:

    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();}
        }
    }
    

  3. It extends SynchronizedCollection, which has the following interesting methods next to the obvious ones:

    // 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!
    }
    

The mutex used here is the same object as to which all operations of Collections.synchronizedSet lock to.

Now we can, judging by the implementation say that it is thread safe to use Collections.synchronizedSet(...).forEach(...), but is it also thread safe by specification?

(Confusingly enough, Collections.synchronizedSet(...).stream().forEach(...) is not thread safe by implementation, and the verdict of the specification seems to be unknown aswell.)

解决方案

As you wrote, judging by implementation, forEach() is thread-safe for the collections provided with JDK (see disclaimer below) as it requires monitor of mutex to be acquired to proceed.

Is it also thread safe by specification?

My opinion - no, and here is an explanation. Collections.synchronizedXXX() javadoc, rewritten in short words, says - "all methods are thread-safe except for those used for iterating over it".

My other, although very subjective argument is what yshavit wrote - unless told/read that, consider API/class/whatever not thread-safe.

Now, let's take a closer look at the javadocs. I guess I may state that method forEach() is used to iterate over it, so, following the advice from javadoc, we should consider it not thread-safe, although it is opposite to reality (implementation).

Anyway, I agree with yshavit's statement that the documentation should be updated as this is most likely a documentation, not implementation flaw. But, no one can say for sure except for JDK developers, see concerns below.

The last point I'd like to mention within this discussion - we can assume that custom collection can be wrapped with Collections.synchronizedXXX(), and the implementation of forEach() of this collection can be... can be anything. The collection might perform asynchronous processing of elements within the forEach() method, spawn a thread for each element... it is bounded only by author's imagination, and synchronized(mutex) wrap cannot guarantee thread-safety for such cases. That particular issue might be the reason not to declare forEach() method as thread-safe..

这篇关于是迭代通过Collections.synchronizedSet(...)。forEach()保证线程安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆