【问题标题】:Is constructor-copy of a synchronized Set thread safe?同步 Set 线程的构造函数副本是否安全?
【发布时间】:2013-07-12 14:04:33
【问题描述】:

获取 java.util.Set 同步版本的最简单方法是使用 Collections.synchronizedSet(),如下所示:

Set mySyncSet = Collections.synchronizedSet(new HashSet());

Java API 谈到了这个新对象:

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

我的问题是,如果我使用这样的复制构造函数创建此 Set 的副本:

Set mySetCopy = new HashMap(mySyncSet);

它是线程安全的吗? (毕竟 HashMap 构造函数不是使用迭代来获取 Set 的成员吗?)还是应该像这样手动同步操作?:

Set mySetCopy;

synchronized(mySyncSet) {
    mySetCopy = new HashMap(mySyncSet);
}

【问题讨论】:

  • Collections.synchronized*() 几乎完全没用,不应该使用。
  • @SLaks 我不同意 Java 内置的同步集合完全没用。这仅取决于您需要(或喜欢)的粒度。
  • @RudolphEst:我想不出所提供的粒度正是您想要的任何操作。

标签: java


【解决方案1】:

让我们看一下代码:

public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

所以只调用addAll

public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    for (E e : c)
        if (add(e))
            modified = true;
    return modified;
}

所以这个循环遍历你给它的Collection

因此答案是否定的,构造函数副本不是线程安全的。

您需要使用第二个选项并在 Set 上执行明确的 synchronized,然后再将其传递给构造函数。

【讨论】:

    【解决方案2】:

    第二种方式更可取。如果某个线程修改了您的原始线程,而该线程正在遍历您的集合以将引用复制到您的新集合,您将遇到麻烦。

    所有访问或修改集合的代码都应该在同一个实例...上手动同步,正如 JavaDoc 所指定的那样。

    【讨论】:

    • 实际上,第二种方式并没有帮助,除非所有其他代码都锁定在同一个实例上。
    • @SLaks 同意,我会在我的回答中说明这一点。我在现实世界的多线程应用程序(主要是 Swing GUI)中使用了同步集合。除了锁定读取/迭代的性能损失之外,Collections.synchronized* 方法很有用。不过,对于性能受限的任务,我建议使用更复杂的锁定/同步系统。
    • @RudolphEst 我完全同意。通过在java.util.concurrentReadWriteLock 中使用Collections 可以大大改进任何重要的synchronization。锁定 Object 仅用于读取访问会导致大量争用。
    猜你喜欢
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多