相对于其他领域的易变语义

发布于 2024-08-03 11:32:48 字数 651 浏览 6 评论 0原文

假设我有以下代码

private volatile Service service;

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

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

Modified 字段标记为易失性,并且其值不依赖于先前的状态。所以,这是正确的多线程代码(暂时不用担心 Service 实现)。

据我所知,从内存可见性的角度来看,读取 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();
  }
}

Suppose I have following code

private volatile Service service;

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

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

Modified field marked as volatile and its value do not depend on previous state. So, this is correct multithreaded code (don't bother about Service implementations for a minute).

As far as I know, reading volatile variable is like entering a lock, from perspective of memory visibility. It's because reading of normal variables can not be reordered with reading volatile variables.

Does this mean that following code is correct?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

扭转时空 2024-08-10 11:32:48

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

无论有或没有易失性(写入对象引用都是原子的),原子性都不是问题,因此您可以以任何方式将其从关注列表中划掉——唯一悬而未决的问题是更改的可见性和顺序的“正确性”。

对易失性变量的任何写入都会设置“发生之前”关系(新 Java 内存模型的关键概念,如 JSR-133) 以及同一变量的任何后续读取。这意味着读取线程必须能够看到写入线程可见的所有内容:也就是说,它必须看到写入时至少具有“当前”值的所有变量。

我们可以通过查看 第 17.4 节来详细解释这一点Java 语言规范 .5,具体如下要点:

  1. “如果 x 和 y 是同一个线程的操作,并且 x 按程序顺序出现在 y 之前,则 hb(x, y)”(即同一线程上的操作不能以与程序顺序不一致的方式重新排序)
  2. “对易失性字段(§8.3.1.4)的写入发生在该字段的每次后续读取之前。” (这是澄清文本,解释易失性字段的先写后读是一个同步点)
  3. “如果 hb(x, y) 和 hb(y, z),则 hb(x, z)”(发生的传递性-before)

因此,在您的示例中:

  • 写入“service”(a) 发生在写入“serviceReady”(b) 之前,由于规则 1,
  • 写入“serviceReady”(b) 发生在读取相同内容之前(c),由于规则 2,
  • 因此,(a) 发生在 (c)(第三条规则)之前,

这意味着在本例中,一旦 serviceReady 为 true,就可以保证“服务”设置正确。

您可以看到一些使用几乎完全相同相同示例的优秀文章,其中一个位于 IBM DeveloperWorks -- 请参阅“Volatile 的新保证”:

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

,并且位于 JSR-133 FAQ,由该 JSR 的作者编写:

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

Yes, this code is 'correct' as it stands, from Java 1.5 onwards.

Atomicity is not a concern, with or without the volatile (writes to object references are atomic), so you can cross that off the concerns list either way -- the only open question is visibility of changes and the 'correctness' of the ordering.

Any write to a volatile variable sets up a 'happens-before' relationship (the key concept of the new Java Memory Model, as specified in JSR-133) with any subsequent reads of the same variable. This means that the reading thread must have visibility into everything visible to the writing thread: that is, it must see all variables with at least their 'current' values at the time of the write.

We can explain this in detail by looking at section 17.4.5 of the Java Language Specification, specifically the following key points:

  1. "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)" (that is, actions on the same thread cannot be reordered in a way to be inconsistent with program order)
  2. "A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field." (this is clarifying text, explaining that write-then-read of a volatile field is a synchronization point)
  3. "If hb(x, y) and hb(y, z), then hb(x, z)" (transitivity of happens-before)

So in your example:

  • the write to 'service' (a) happens-before the write to 'serviceReady' (b), due to rule 1
  • the write to 'serviceReady' (b) happens-before the read of same (c), due to rule 2
  • therefore, (a) happens-before (c) (3rd rule)

meaning that you are guaranteed that 'service' is set correctly, in this instance, once serviceReady is true.

You can see some good write-ups using almost exactly the same example, one at IBM DeveloperWorks -- see "New Guarantees for Volatile":

values that were visible to A at the time that V was written are guaranteed now to be visible to B.

and one at the JSR-133 FAQ, written by the authors of that JSR:

Thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it. This would not have been true under the old memory model. If v were not volatile, then the compiler could reorder the writes in writer, and reader's read of x might see 0.

つ可否回来 2024-08-10 11:32:48

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

AFAIK this is correct code.

@CPerkins: making the only the setService method synchronized won't work as you also have to synchronize on reads.

However, one variable would be enough in this case. Why do you need the extra boolean field.
E.g.

private volatile Service service;

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

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

given that no one ever calls setService to null. So you should probably a null-check:

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();
  }
}
萌能量女王 2024-08-10 11:32:48

您对易失性的影响是正确的,所以这应该是正确的,但我对您的设计感到困惑。我不明白为什么你不只是同步 setService - 它可能不会经常被调用。如果它被多次调用,“if (serviceReady)”部分就没有意义了,因为它仍然是真的,但没关系,因为如果我理解正确的话,替换是原子的。

我猜 service.doWork() 是线程安全的,是吗?

You're right about the effects of volatile, so this should be correct, but I'm confused about your design. I don't understand why you don't just synchronize setService - it probably wouldn't be called often. If it is called more than once, the "if (serviceReady)" part is moot, since it'll still be true, but that's fine, since the replacement is atomic, if I understand correctly.

I gather that service.doWork() is thread-safe, yes?

十级心震 2024-08-10 11:32:48

从理论上讲,它永远不会起作用。您希望确保两个变量的内存一致性,并且希望依赖第一个变量的易失性读取。易失性只读保证读取线程看到变量的最新值。所以它绝对不如进入锁定(同步)部分那么强。

实际上,它可能会起作用,具体取决于您所使用的 JVM 的 volatile 实现。如果通过刷新所有 CPU 缓存来实现易失性读取,那么它应该可以工作。但我准备打赌这不会发生。 我可以在多核 x86 CPU 上强制实现缓存一致性吗?< /a> 是关于这个主题的好读物。

我想说,只需为两个变量设置一个公共锁( java.util.concurrent.Lock 或 synchronized )即可完成。


Java 语言规范,第三版,有这个说说易失性:

8.3.1.4 易失性字段

字段可以声明为易失性的,在这种情况下,Java 内存模型(第 17 节)确保所有线程看到变量的一致值。

17.4.4 同步顺序

  • 对 易失性变量 (§8.3.1.4) v 的写入与任何线程对 v 的所有后续读取进行同步(其中后续是根据同步顺序定义的)。
  • 对易失性字段 (§8.3.1.4) 的写入发生在该字段的每次后续读取之前。

它没有提及其他变量的可见性影响。

In theory, it should never work. You want to insure memory consistency for two variables, and you want to rely on a volatile read on the first one. The volatile read only guarantees that a reading thread sees the most recent value of the variable. So it's definitely not as strong as entering a locked ( synchronized ) section.

In practice, it might work, depending on the implementation of volatile by the JVM you're using. If volatile reads are implemented by flushing all CPU caches, it should work. But I'm ready to bet that it will not happen. Can I force cache coherency on a multicore x86 CPU? is a good read regarding this topic.

I would say simply have a common lock ( java.util.concurrent.Lock or synchronized ) for the two variables and be done with it.


The Java Language Specification, Third Edition, has this to say about volatile:

8.3.1.4 volatile Fields

A field may be declared volatile, in which case the Java memory model (§17) ensures that all threads see a consistent value for the variable.

and

17.4.4 Synchronisation order

  • A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).
  • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

It says nothing about the visibility effect of other variables.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文