同步,易失性和(已加盖)锁的Java内存模型交互

时间:2018-09-19 16:30:15

标签: java multithreading concurrency java-memory-model

使用锁来保证内存可见性时是否需要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();
}

3 个答案:

答案 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中volatilesynchronized块之间的主要区别之一。因此,当您使用synchronized块时,变量不必为volatile。但是,如果您的变量是volatile并对该变量执行任何复合操作,则需要使用锁来保护对volatile变量的更新。

  

该部分是否表示随后的同步方法调用将保护同一个变量,以确保该变量对第二个线程可见?如果是这种情况,由于我们也可以保证订单顺序,因此锁也一样吗?

是的。因为锁将为您提供可见性和原子性。

  

另一方面,当我们突然有了允许两个线程访问该字段的写锁时,会发生什么情况。整个结构是否会崩溃,即使变量被解锁,也永远不能保证线程会更新其缓存?

如果您保护同一锁上变量的更新,则在任何给定时间只有一个线程可以对该变量进行操作。因此,它保证了一致性。但是,如果您每次都使用不同的锁来保护该变量,则多个线程将修改变量状态,并可能使变量状态不一致。因此,在这种情况下,可见性和原子性都得到了保证,但仍然可能导致不一致。