处理后续网络请求的线程是否能保证看到在先前请求期间写入的 volatile 变量的值?

Would a thread handling a subsequent network request be guaranteed to see the value of a volatile variable written during a previous request?

我有这个关于 Java 内存模型的理论问题。假设我有一个带有以下两个请求处理程序的服务器 class:

class MyHandlers {
  volatile int someFlag = 0;

  String handleFirstRequest() {
    someFlag = 1;
    return "Hello!";
  }

  String handleSecondRequest() {
    return "value of someFlag: " + someFlag;
  }
}

我也有一个客户。我的客户端发送触发执行 handleFirstRequest 的网络请求。客户端等待请求完成。第一个请求完成后,客户端发送触发 handleSecondRequest 的第二个请求。

问题:Java内存模型如何防止对第二个请求的响应"value of someFlag: 0"

注意:据我所知,在实践中,处理第二个响应的线程总是将 someFlag 视为 1。

如果我正确读取了 JMM,则有一个同步顺序是一个总顺序,在我的示例中将对易失性读取和易失性写入 (someFlag = 1) 进行排序。如果读取在写入之后,则读取将看到写入。是否有可能出现写在读之后的情况?在这种情况下,写入不会与读取同步,并且写入和读取之间不会发生先行关系。这将导致处理第二个请求的线程将 someFlag 视为 0。我的理解哪里出错了?

补充思考(2020年3月2日): JMM没有提到时间的概念。同步动作是根据同步顺序排序的,但是JMM中没有任何内容说同步顺序与按时间排序的动作顺序相同。这表明 Java 实现可能会在 someFlag 写入之前命令读取 即使读取发生在 之后 写入根据时钟。似乎 JMM 只保证 if 易失性读取在易失性写入之后排序,然后易失性写入之前的写入对易失性读取之后的读取可见。

如果第一个请求完成则意味着 someFlag = 1 已被某个线程执行。在这一点上,someFlag 的值保证对执行读取的任何其他线程可见。因此,当第二个请求到来时,您可以确定它会看到值 1。

根据你的描述,你是在request1完成后才发送request2,所以request2是肯定得到someFlag = 1


另一种情况:线程A刚开始handleFirstRequest()还没结束,线程B来读取someFlag,B会得到someFlag = 0

是的,有可能,但可能性非常非常小...

JMM中的8个原子操作你可能看过:

  • 锁定\解锁
  • 读\写
  • 加载\存储
  • 使用\分配

是的,每个 JMM 操作都具有原子性特征,但是,在有序的 JMM 执行中它们不能是一个原子。如果线程想要更​​改 someFlag,他将读取 -- load -- em... 和一个很多jmm操作,所有的分离操作都没有原子性。


volatile 有什么作用?它使用CAS来防止多线程冲突,并启用Bus可以感知任何JMM写操作,然后使所有线程已读取的写入字段无效。当加载的字段无效时,线程将更新它马上。

通常,每个线程在硬件中的某处都有自己的缓存。读取和写入通常在此缓存中进行,并在稍后的某个时间(当内存行需要离开缓存时)写回到主内存中。这就是为什么两个不同的线程可能会看到相同变量的不同值的原因。

volatile 关键字可以防止这种情况发生。 volatile 值永远不会被缓存,所有的读写都必须在主内存中完成。作为奖励,volatile 值也是原子读取和写入的。

因此,当第一个线程将 someFlag 更新为 1 时,它会立即对所有线程可见(代价是由于缓存预防而导致性能降低)。然后,当第二个线程读取它时,它会看到第一个线程给的值。

由于客户端等待第一个请求完成才能启动第二个请求,因此您确定第一个请求发生在第二个请求之前。

综上所述,第二个请求无法将 someFlag 视为 0。

我找到答案了!

Section 17.4.3 of the Java Language Specification 声明如下:

Sequential consistency is a very strong guarantee that is made about visibility and ordering in an execution of a program. Within a sequentially consistent execution, there is a total order over all individual actions (such as reads and writes) which is consistent with the order of the program, and each individual action is atomic and is immediately visible to every thread.

在Java内存模型中,"actions"指的是线程间操作,其中包括易失性写入。本段保证任何符合 JLS 的 Java 实现都保证 volatile 写入将立即对其他线程可见。在开头的例子中post,这段保证volatile read不能在volatile write之前排序