Java - Lock保证如何在关系之前发生?

时间:2017-04-22 09:06:57

标签: java multithreading concurrency locking volatile

让我们考虑Java中的以下标准同步:

public class Job {
   private Lock lock = new ReentrantLock();

   public void work() {
       lock.lock();
       try {
           doLotsOfWork();
       } finally {
           lock.unlock();
       }
   }
}

据我所知,基于Javadoc,这相当于synchronized块。我很难看到这是如何在较低级别实际执行的。

Lock有一个易失性状态,在调用lock()时会执行易失性读取,然后在释放时执行易失性写入。如何写入一个对象的状态确保{{1>}的{strong>没有任何指令可能触及许多不同的对象,不会无序执行?

或者想象doLotsOfWork实际上是用1000多行代码代替的。显然,编译器无法事先知道锁内部存在易失性,因此需要停止重新排序指令。那么,如果在doLotsOfWork保证之前发生了怎样的事情,即使它是围绕一个单独的对象的易失性状态构建的呢?

2 个答案:

答案 0 :(得分:2)

好吧,如果我理解正确,那么你的回答是here。易失性写入和读取会引入内存障碍LoadLoadLoadStore禁止重新排序。在CPU级别,这会转换为实际的内存障碍,如mfencelfence(CPU也会通过其他一些机制强制进行非重新排序,因此您可能会在机器代码中看到其他内容。 )。

这是一个小例子:

i = 42;
j = 53;
[StoreStore]
[LoadStore]
x = 1; // volatile store

ij分配可以在那之间重新排序,但他们不能x=1或换句话说i and j可以不要低于x。

同样适用于volatile reads

对于您的示例,doLotsOfWork中的每个操作都可以按照编译器的要求重新排序,但不能与lock operations重新排序。

另外,当你说编译器不能知道有volatile read/write时,你就会有点错误。 必须知道,否则就没有其他方法可以阻止这些重新排序。

另外,最后一点:从jdk-8开始,您可以通过Unsafe强制执行非重新排序,除了易变之外,还提供了相应的方法。

答案 1 :(得分:1)

来自Oracle的documentation

  

volatile字段的写入发生在每次后续读取之前   那个领域。 volatile字段的写入和读取具有相似之处   进入和退出监视器时的内存一致性效果,但确实如此   需要互斥锁定。

Java Concurrency in Practice 更明确地说明了这一点:

  

volatile变量的可见性效果超出了值   volatile变量本身。当线程A 写入volatile时   变量和随后线程B 读取相同的变量,   在写入之前 A 可见的所有变量的值   阅读volatile后,{em> B 可见volatile变量   变量

应用于ReentrantLock,这意味着在lock.unlock()(在您的情况下为doLotsOfWork())之前执行的所有内容都将保证后续调用{{1}之前发生}}。 lock.lock()内的说明仍然可以在它们之间进行重新排序。这里唯一保证的是,随后获取锁定调用{​​{1}}的任何线程都会在调用doLotsOfWork()之前看到lock.lock()中所做的所有更改。