使用锁来保证内存可见性时是否需要 volatile
修饰符?
在尝试完全理解并发性、内存可见性和执行控制时,我遇到了多个消息来源,称在 synchronized
block 中更新的变量不需要该字段是 volatile
(大多数情况下)没有给出任何来源,实际上有一页说同步方法和波动性字段需要结合使用)。
当接近jls chapter 17.4.5时我发现:
Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.
这部分是说后续同步方法调用保护相同的变量将确保它对第二个线程可见吗?如果是这种情况,那么锁也同样适用吗,因为我们也可以保证顺序?
另一方面,当我们突然拥有允许 2 个线程访问该字段的写锁时会发生什么情况。即使变量被解锁,整个构造是否会崩溃并且线程永远无法保证更新其缓存?
简短的代码
int field; //volatile not needed because we have a definite happens-before relationship
Lock lock;
void update(){
//No matter how many threads access this method they will always have
//the most up to date field value to work with.
lock.lock()
field *= 2;
lock.unlock();
}
最佳答案
摘自Lock
的API文档:
https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/locks/Lock.html
All Lock implementations must enforce the same memory synchronization semantics as provided by the built-in monitor lock, as described in Chapter 17 of The Java™ Language Specification:
- A successful lock operation has the same memory synchronization effects as a successful Lock action.
- A successful unlock operation has the same memory synchronization effects as a successful Unlock action.
Unsuccessful locking and unlocking operations, and reentrant locking/unlocking operations, do not require any memory synchronization effects.
在我看来,这有点不清楚,但要点是,是的,Lock
需要以与监视器相同的方式工作(synchronized
关键字的作用),并且因此,您的示例始终使 field
的最新更新可见,而无需显式使用 volatile
关键字。
附注获取 Brian Goetz 的Java 并发实践,它更详细地解释了所有这些内容。它基本上是 Java 并发方面的圣经。
关于同步、 volatile 和(标记)锁的 Java 内存模型交互,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52410444/