与其他领域相关的易变语义

Den*_*nov 13 java concurrency volatile

假设我有以下代码

private volatile Service service;

public void setService(Service service) {
  this.service = service;
}

public void doWork() {
  service.doWork();
}
Run Code Online (Sandbox Code Playgroud)

修改后的字段标记为volatile,其值不依赖于先前的状态.所以,这是正确的多线程代码(不用担心Service一分钟的实现).

据我所知,从内存可见性的角度来看,读取volatile变量就像输入一个锁.这是因为读取常规变量不能通过读取volatile变量来重新排序.

这是否意味着以下代码是正确的?

private volatile boolean serviceReady = false;
private Service service;

public void setService(Service service) {
  this.service = service;
  this.serviceReady = true;
}

public void doWork() {
  if ( serviceReady ) {
    service.doWork();
  }
}
Run Code Online (Sandbox Code Playgroud)

Cow*_*wan 18

是的,从Java 1.5开始,这段代码是"正确的".

原子性不是一个问题,有或没有volatile(写入对象引用是原子的),所以你可以通过任何方式跨越关注列表 - 唯一的开放问题是变化的可见性和排序的'正确性'.

对volatile变量的任何写入都会建立一个'发生在'之前的关系(新的Java内存模型的关键概念,如JSR-133中所指定的)以及对同一变量的任何后续读取.这意味着读取线程必须能够看到写入线程可见的所有内容:也就是说,它必须在写入时查看至少具有"当前"值的所有变量.

我们可以通过查看Java语言规范的第17.4.5节详细解释这一点,特别是以下要点:

  1. "如果x和y是同一个线程的动作,并且x在程序顺序中出现在y之前,那么hb(x,y)"(即,同一线程上的动作不能以与程序顺序不一致的方式重新排序)
  2. "在对该字段的每次后续读取之前,都会发生对易失性字段(第8.3.1.4节)的写入." (这是澄清文本,解释写入然后读取易失性字段是同步点)
  3. "如果hb(x,y)和hb(y,z),那么hb(x,z)"(发生之前的传递性)

所以在你的例子中:

  • 由于规则1的原因,在写入'serviceReady'(b)之前写入'service'(a)
  • 由于规则2,对'serviceReady'(b)的写入发生在读取相同(c)之前
  • 因此,(a)发生在(c)之前(第3条规则)

这意味着,一旦serviceReady为true,就可以保证正确设置'service'.

您可以使用几乎完全相同的示例(IBM DeveloperWorks中的一个示例)看到一些好的文章- 请参阅"新保证的易失性":

现在保证在写入V时A可见的值对B可见.

JSR-133 FAQ中的一个,由JSR的作者撰写:

因此,如果读者看到v的值为true,那么也可以保证看到在它之前发生的写入42.在旧的内存模型下,这不可能是真的.如果v不是volatile,那么编译器可以重新排序writer中的写入,读者对x的读取可能会看到0.