Collections.synchronizedCollection的forEach和removeIf中可能存在错误?

时间:2014-05-04 11:45:40

标签: java collections thread-safety java-8

作为我上一篇文章Is iteration via Collections.synchronizedSet(...).forEach() guaranteed to be thread safe?的后续内容,我将分享我对我认为实施中的错误的看法,以验证它确实是一个错误。

我们这里有一个SynchronizedCollection<E>,可以从调用Collections.synchronizedCollection(...)获得,来自JDK:

public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
}

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 3053995032091335093L;

    final Collection<E> c;  // Backing Collection
    final Object mutex;     // Object on which to synchronize

    SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
    }

    SynchronizedCollection(Collection<E> c, Object mutex) {
        this.c = Objects.requireNonNull(c);
        this.mutex = Objects.requireNonNull(mutex);
    }

    public int size() {
        synchronized (mutex) {return c.size();}
    }
    public boolean isEmpty() {
        synchronized (mutex) {return c.isEmpty();}
    }
    public boolean contains(Object o) {
        synchronized (mutex) {return c.contains(o);}
    }
    public Object[] toArray() {
        synchronized (mutex) {return c.toArray();}
    }
    public <T> T[] toArray(T[] a) {
        synchronized (mutex) {return c.toArray(a);}
    }

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

    public boolean add(E e) {
        synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}
    }

    public boolean containsAll(Collection<?> coll) {
        synchronized (mutex) {return c.containsAll(coll);}
    }
    public boolean addAll(Collection<? extends E> coll) {
        synchronized (mutex) {return c.addAll(coll);}
    }
    public boolean removeAll(Collection<?> coll) {
        synchronized (mutex) {return c.removeAll(coll);}
    }
    public boolean retainAll(Collection<?> coll) {
        synchronized (mutex) {return c.retainAll(coll);}
    }
    public void clear() {
        synchronized (mutex) {c.clear();}
    }
    public String toString() {
        synchronized (mutex) {return c.toString();}
    }
    // 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!
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        synchronized (mutex) {s.defaultWriteObject();}
    }
}

现在让我们再看一下这段代码:

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

此实现中的方法允许插入任何代码

现在我将控制权转移到 Effective Java:第67项:过度同步

  

...
  为避免活跃和安全失败,请勿在同步方法或块中将控制权交给客户。 ...从具有同步区域的类的角度来看,此类方法 alien 。 ...根据异类方法的作用,从同步区域调用它可能会导致异常,死锁或数据损坏   ...

(重点作者)

来自forEach的{​​{1}}和removeIf方法看起来完全违反了该规则,我认为该规则在Java 8中仍然有效。

所以我们应该能够在这里构建两个死锁的SSCEE。

我将此标记为可能的错误的原因之一是,我无法使用以下代码重现它:

Collections.synchronizedCollection

按预期打印public class Java8BugSSCEE1 { public static void main(String[] args) { Collection<String> collection = Collections.synchronizedCollection(new HashSet<>()); collection.add("Test"); collection.forEach(str -> { synchronized (collection) { System.out.println("Obtained lock"); collection.add(str + Integer.toHexString(ThreadLocalRandom.current().nextInt(16))); } }); System.out.println("collection = " + collection); } } ,其中[Test, TestX]为十六进制。

我的想法是:

  1. X in [0, f]将锁定SynchronizedCollection.forEach,这正是返回的mutex
  2. 然后在我的lambda里面,我会尝试获得同样的锁并失败。
  3. 然而,最后一步没有发生,并且该程序正常执行。

    所以最后一个问题,Java 8实现中是否存在错误?

    编辑,我有新的SSCEE,可能确实显示了实施中的错误。

    collection

    我希望这个版本能够添加一个元素,但它不会添加任何内容。

1 个答案:

答案 0 :(得分:10)

这不是一个错误。这是一个功能。 synchronizedCollection()返回的集合使其所有方法同步,这就是全部。它是记录在案的行为。

这会自动使您的代码线程安全吗?没有。

这是否可以防止任何死锁的可能性?没有。

调用者应该了解他为防止死锁并使其代码线程安全所做的事情吗?肯定。

为什么你的代码不会失败?因为锁是可重入的。您可以让一个线程多次锁定同一个对象而不会出现问题。这就是你的代码所做的事情。要有死锁,你需要多个线程。

编辑:

关于您上次的SSCCE:您在不等待添加线程完成其工作的情况下重新打印该集合。因此,您有一个竞争条件:添加线程可能已完成,或者尚未启动。错误发生在您的代码中,而不是集合的代码中。