使用锁来保证内存可见性时是否需要volatile
修饰符?
试图完全理解并发性,内存可见性和执行控制,我遇到了几个来源,说在synchronized
块中更新的变量不需要将字段设为volatile
(大多数情况下没有给出来源,实际上是页面上说同步方法和波动率字段需要结合使用。
接近jls chapter 17.4.5时,我发现:
可以通过先发生后关系来命令两个动作。如果一个动作 -在另一个之前发生,然后第一个对第二个可见并在第二个之前排序。
该部分是否说过,随后的同步方法调用将保护同一个变量变量,以确保该变量对第二个线程可见?如果是这种情况,由于我们也可以保证订单顺序,因此锁也一样吗?
另一方面,当我们突然有了允许两个线程访问该字段的写锁时,会发生什么情况。整个结构是否会崩溃,即使变量被解锁,也永远不能保证线程会更新其缓存?
简而言之
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();
}
答案 0 :(得分:2)
摘自Lock
的API文档:
https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/locks/Lock.html
所有Lock实现必须强制执行相同的内存同步 内置监视器锁提供的语义,如 Java™语言规范的第17章:
- 成功的锁定操作与成功的锁定操作具有相同的内存同步效果。
- 成功的解锁操作与成功的解锁操作具有相同的内存同步效果。
锁定和解锁操作失败,并且无法重入 锁定/解锁操作,不需要任何内存 同步效果。
这是一个不太明确的imo,但要点是,是的,Lock
的工作方式与监视器相同(synchronized
关键字的工作方式),因此您的示例始终会无需显式使用field
关键字即可看到volatile
的最新更新。
P.S。获取Brian Goetz的 Java并发实践,它会更详细地解释所有这些内容。基本上,这是Java中所有事物并发的圣经。
答案 1 :(得分:0)
...实际上一页上需要说明同步方法和易变性字段。
您可以提取关于内存可见性所需的所有知识,并将synchronized
简化为一条简单的规则。也就是说,在线程B进入synchronized (o) {...}
块的同时,保证线程A从synchronized (o) {...}
块退出之前对共享变量和对象所做的任何操作都对线程B可见。对象, o
。
而且,正如@markspace已经说过的,java.util.concurrent.locks.Lock
的任何实现都必须以相同的方式工作。
答案 2 :(得分:0)
使用锁来确保内存可见性时是否需要使用volatile修饰符?
volatile
变量仅保证内存可见性,而不保证原子性。这是Java中volatile
和synchronized
块之间的主要区别之一。因此,当您使用synchronized
块时,变量不必为volatile
。但是,如果您的变量是volatile
并对该变量执行任何复合操作,则需要使用锁来保护对volatile
变量的更新。
该部分是否表示随后的同步方法调用将保护同一个变量,以确保该变量对第二个线程可见?如果是这种情况,由于我们也可以保证订单顺序,因此锁也一样吗?
是的。因为锁将为您提供可见性和原子性。
另一方面,当我们突然有了允许两个线程访问该字段的写锁时,会发生什么情况。整个结构是否会崩溃,即使变量被解锁,也永远不能保证线程会更新其缓存?
如果您保护同一锁上变量的更新,则在任何给定时间只有一个线程可以对该变量进行操作。因此,它保证了一致性。但是,如果您每次都使用不同的锁来保护该变量,则多个线程将修改变量状态,并可能使变量状态不一致。因此,在这种情况下,可见性和原子性都得到了保证,但仍然可能导致不一致。