- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
想象有一个主线程创建一个 HashSet 并启动许多工作线程将 HashSet 传递给它们。
就像下面的代码:
void main() {
final Set<String> set = new HashSet<>();
final ExecutorService threadExecutor =
Executors.newFixedThreadPool(10);
threadExecutor.submit(() -> doJob(set));
}
void doJob(final Set<String> pSet) {
// do some stuff
final String x = ... // doesn't matter how we received the value.
if (!pSet.contains(x)) {
synchronized (pSet) {
// double check to prevent multiple adds within different threads
if (!pSet.contains(x)) {
// do some exclusive work with x.
pSet.add(x);
}
}
}
// do some stuff
}
我想知道仅在 add 方法上同步是否线程安全? contains
不同步会不会有什么问题?
我的直觉告诉我这很好,在离开同步块(synchronized block)后对 set 所做的更改应该对所有线程可见,但 JMM 有时可能违反直觉。
附言我不认为它是 How to lock multiple resources in java multithreading 的副本尽管这两个问题的答案可能相似,但这个问题针对的是更特殊的情况。
最佳答案
I'm wondering is it thread-safe to synchronize only on the
add
method? Are there any possible issues ifcontains
is not synchronized as well?
简短的回答:否和是。
有两种解释方式:
Java 同步(以其各种形式)防止许多事情,包括:
在您的示例中,同步add
足以确保两个线程不能同时更新HashSet
,并且两个调用都将是在最近的 HashSet
状态上运行。
但是,如果 contains
也未同步,则 contains
调用可能会与 add
调用同时发生。这可能导致 contains
调用看到 HashSet
的中间状态,从而导致不正确的结果,或者更糟。如果调用不是同时发生的,这也会发生,因为更改没有立即刷新到主内存和/或读取线程没有从主内存读取。
JLS 指定了 Java 内存模型,该模型规定了多线程应用程序必须满足的条件,以保证一个线程可以看到另一个线程所做的内存更新。该模型是用数学语言表达的,不容易理解,但要点是当且仅当从写入到后续读取之间存在一系列发生在之前的关系时,才能保证可见性。如果写入和读取在不同的线程中,则线程之间的同步是这些关系的主要来源。例如在
// thread one
synchronized (sharedLock) {
sharedVariable = 42;
}
// thread two
synchronized (sharedLock) {
other = sharedVariable;
}
假设线程一代码在线程二代码之前运行,则在线程一释放锁和线程二获取锁之间存在先于关系。有了这个和“程序顺序”的关系,我们就可以构建一个从42
的写入到赋值给other
的链条。这足以保证 other
将被分配 42
(或者可能是变量的后续值)并且在 sharedVariable
中没有任何值 42
被写入其中。
如果 synchronized
block 在同一个锁上同步,第二个线程可能会看到 sharedVariable
的陈旧值;即在 42
分配给它之前写入了一些值。
关于java - 仅在添加到 HashSet 时同步是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57360374/
我是一名优秀的程序员,十分优秀!