让我们考虑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
保证之前发生了怎样的事情,即使它是围绕一个单独的对象的易失性状态构建的呢?
答案 0 :(得分:2)
好吧,如果我理解正确,那么你的回答是here。易失性写入和读取会引入内存障碍:LoadLoad
,LoadStore
等禁止重新排序。在CPU级别,这会转换为实际的内存障碍,如mfence
或lfence
(CPU也会通过其他一些机制强制进行非重新排序,因此您可能会在机器代码中看到其他内容。 )。
这是一个小例子:
i = 42;
j = 53;
[StoreStore]
[LoadStore]
x = 1; // volatile store
i
和j
分配可以在那之间重新排序,但他们不能与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()
中所做的所有更改。