相对于其他字段的可变语义

2022-09-03 13:26:58

假设我有以下代码

private volatile Service service;

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

public void doWork() {
  service.doWork();
}

标记为易失性的已修改字段及其值不依赖于以前的状态。所以,这是正确的多线程代码(不要为实现而烦恼)。Service

据我所知,从内存可见性的角度来看,读取易失性变量就像进入锁一样。这是因为正常变量的读取不能与读取可变变量重新排序。

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

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();
  }
}

答案 1

是的,从Java 1.5开始,这段代码是“正确的”。

原子性不是一个问题,无论有没有易失性(写入对象引用都是原子的),所以你可以用任何一种方式将其从关注列表中划掉 - 唯一悬而未决的问题是变化的可见性和排序的“正确性”。

对易失性变量的任何写入都会建立“发生前”关系(新 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,写到“服务”(a)发生在写到“服务准备好”(b)之前
  • 由于规则2,对“服务就绪”(b)的写入发生在读取相同(c)之前
  • 因此,(a) 发生在 (c) 之前(第 3 条规则)

这意味着您保证“服务”设置正确,在这种情况下,一旦serviceReady为真。

你可以看到一些使用几乎完全相同的例子的好文章,一个在IBM DeveloperWorks上 - 参见“Volatile的新保证”:

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

还有一个在 JSR-133 FAQ 上,由该 JSR 的作者编写:

因此,如果读者看到 v 的值为 true,则还可以保证看到之前发生的写入 42。在旧的记忆模型下,情况并非如此。如果 v 不是易失性的,那么编译器可以在 writer 中对写入重新排序,并且读者对 x 的读取可能会看到 0。


答案 2

AFAIK 这是正确的代码。

@CPerkins:使唯一同步的方法不起作用,因为您还必须在读取时进行同步。setService

但是,在这种情况下,一个变量就足够了。为什么需要额外的布尔字段。例如:

private volatile Service service;

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

public void doWork() {
  if ( service != null ) {
    service.doWork();
  }
}

鉴于没有人将 setService 调用为 。所以你应该进行空检查:null

private volatile Service service;

public void setService(Service service) {
  if (service == null) throw NullPointerException();
  this.service = service;
}

public void doWork() {
  if ( service != null ) {
    service.doWork();
  }
}

推荐