如果此方法的变量'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();
}
使用final,'commonSet'的引用被'锁定'。但是在其上运行的多个线程仍然可以破坏集合中的值(它可能包含重复项?)。其次,混淆是因为'commonSet'是一个方法级变量 - 它的相同引用将在调用方法的堆栈内存(threadCreatorFunction)和运行方法的堆栈内存上 - 这是正确的吗?
有很多与此相关的问题:
但是,我看不到他们强调线程安全部分这种共享/传递可变性。
答案 0 :(得分:9)
不,这绝对不是线程安全的。只是因为你已经在最后一个变量中得到它,这意味着两个线程都会看到相同的引用,这很好 - 但它不会使对象任何更加线程安全。
您需要同步访问权限,或使用ConcurrentSkipListSet
。
答案 1 :(得分:5)
一个有趣的例子。
引用commonSet
是线程安全且不可变的。它位于第一个线程的堆栈和匿名Runnable
类的字段中。 (您可以在调试器中看到这一点)
set commonSet
引用是可变的而不是线程安全的。您需要使用synchronized或Lock来使其线程安全。 (或者使用线程安全集合)
答案 2 :(得分:1)
我认为你在第一句话中遗漏了一句话:
如果此方法的变量'commonSet'是
???
而不是类级字段,则以下代码会导致相同的问题。
我觉得你有点困惑。并发问题与是否声明对可变数据结构的引用final
无关。您需要将引用声明为final
,因为您在Runnable
的匿名内部类声明中closing over。如果您实际上要让多个线程读/写数据结构,那么您需要使用锁(同步)或使用并发数据结构,如java.util.concurrent.ConcurrentHashMap。
答案 3 :(得分:1)
commonSet在两个Thread之间共享。您已将其声明为final,因此您使引用不可变(您无法重新分配),但Set中的实际数据仍然是可变的。假设一个Thread放入一些数据,而另一些Thread读取一些数据。每当第一个线程放入数据时,您很可能想要锁定该Set,以便在写入该数据之前没有其他线程可以读取。 HashSet会发生这种情况吗?不是真的。
答案 4 :(得分:0)
正如其他人已经评论过的那样,你误解了一些概念,比如final和synchronized。
我认为如果你用你的代码解释你想要完成什么,那么帮助你会容易得多。我的印象是,此代码段更像是实际代码的示例。
一些问题:为什么在函数内部定义了集合?它应该在线程之间共享吗?令我困惑的是你用同一个runnable实例创建两个线程
new Thread(runnable, "T_A").start();
new Thread(runnable, "T_B").start();
答案 5 :(得分:0)
无论是单线程还是多线程使用commonset,它只是对最终对象不可变的引用(即,一旦分配,你不能再分配另一个obj引用),但你仍然可以使用该引用修改此对象引用的内容
如果它不是最终的,则一个线程可以再次初始化它并更改引用
commonSet = new HashSet<String>();
commonSet.add(newValue());
在这种情况下,这两个线程可能使用两个不同的公共集,这可能不是你想要的