8

如果此方法的变量“commonSet”改为类级别字段,则以下代码是否会导致相同的问题。如果它是一个类级别的字段,我将不得不在同步块中包装添加到设置操作,因为 HashSet 不是线程安全的。我是否应该在下面的代码中做同样的事情,因为多个线程正在添加到集合中,甚至当前线程可能会继续改变集合。

public void threadCreatorFunction(final String[] args) {
    final Set<String> commonSet = new HashSet<String>();

    final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                commonSet.add(newValue());
            }
        }
    };

    new Thread(runnable, "T_A").start();
    new Thread(runnable, "T_B").start();
}

对“commonSet”的引用通过使用 final 被“锁定”。但是在它上面运行的多个线程仍然可以破坏集合中的值(它可能包含重复项?)。其次,混淆是因为“commonSet”是一个方法级别的变量——它的相同引用将在调用方法(threadCreatorFunction)的堆栈内存和运行方法的堆栈内存上——这是正确的吗?

有很多与此相关的问题:

但是,我看不到他们强调这种共享/传递可变变量的线程安全部分。

4

6 回答 6

9

不,这绝对不是线程安全的。仅仅因为您将它放在 final 变量中,这意味着两个线程将看到相同的引用,这很好 - 但它不会使对象变得更加线程安全。

要么您需要同步访问,要么使用ConcurrentSkipListSet.

于 2012-08-06T07:44:53.230 回答
5

一个有趣的例子。

引用commonSet是线程安全且不可变的。它位于第一个线程的堆栈上,也是您的匿名Runnable类的一个字段。(您可以在调试器中看到这一点)

commonSet所指的集合是可变的并且不是线程安全的。您需要使用同步或锁来使其线程安全。(或者改用线程安全的集合)

于 2012-08-06T07:44:55.163 回答
1

我认为你在第一句话中漏掉了一个词:

如果此方法的变量“commonSet”是???类级别字段,则以下代码是否会导致相同的问题。

我觉得你有点困惑。并发问题与是否声明了对可变数据结构的引用无关final。您需要将引用声明为因为finalRunnable. 如果您实际上要让多个线程读取/写入数据结构,那么您需要使用锁(同步)或使用并发数据结构,如java.util.concurrent.ConcurrentHashMap

于 2012-08-06T07:50:34.000 回答
1

commonSet 在两个线程之间共享。您已将其声明为 final,因此您使引用不可变(您不能重新分配它),但 Set 中的实际数据仍然是可变的。假设一个线程放入一些数据,而另一个线程读取一些数据。每当第一个线程放入数据时,您很可能希望锁定该 Set,以便在写入该数据之前没有其他线程可以读取。HashSet会发生这种情况吗?并不真地。

于 2012-08-06T08:35:12.170 回答
0

正如其他人已经评论的那样,您误会了一些概念,例如最终和同步。

我认为如果你解释你想用你的代码完成什么,帮助你会容易得多。我的印象是这个代码片段比实际代码更像是一个例子。

一些问题:为什么在函数内部定义集合?它应该在线程之间共享吗?令我困惑的是,您使用相同的可运行实例创建了两个线程

    new Thread(runnable, "T_A").start();
    new Thread(runnable, "T_B").start();
于 2012-08-06T08:06:39.930 回答
0

无论 commonset 是由单个线程还是多个线程使用,它只是对最终对象不可变的引用(即,一旦分配,您就不能再次分配另一个 obj 引用)但是您仍然可以使用该引用修改该对象引用的内容。

如果它不是最终的,一个线程可能会再次对其进行初始化并更改引用 commonSet = new HashSet<String>(); commonSet.add(newValue()); ,在这种情况下,这两个线程可能会使用两个不同的公共集,这可能不是您想要的

于 2012-08-06T08:14:19.830 回答