为什么 Unsafe.fullFence() 不能确保我的示例中的可见性?

2023-12-07

我正在尝试深入研究volatileJava 中的关键字和设置 2 测试环境。我相信它们都使用 x86_64 并使用热点。

Java version: 1.8.0_232
CPU: AMD Ryzen 7 8Core

Java version: 1.8.0_231
CPU: Intel I7

代码在这里:

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class Test {

  private boolean flag = true; //left non-volatile intentionally
  private volatile int dummyVolatile = 1;

  public static void main(String[] args) throws Exception {
    Test t = new Test();
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);

    Thread t1 = new Thread(() -> {
        while (t.flag) {
          //int b = t.someValue;
          //unsafe.loadFence();
          //unsafe.storeFence();
          //unsafe.fullFence();
        }
        System.out.println("Finished!");
      });

    Thread t2 = new Thread(() -> {
        t.flag = false;
        unsafe.fullFence();
      });

    t1.start();
    Thread.sleep(1000);
    t2.start();
    t1.join();
  }
}

“完成的!”从未被打印过,这对我来说没有意义。我期待着fullFence在线程 2 中使flag = false全球可见。

根据我的研究,Hotspot 使用lock/mfence实施fullFence在 x86 上。并根据Intel mfence的指令集参考手册入门

此串行化操作保证按程序顺序位于 MFENCE 指令之前的每个加载和存储指令在 MFENCE 指令之后的任何加载或存储指令之前变得全局可见。

甚至“更糟”,如果我评论出来fullFence在线程 2 中并取消注释任一xxxFence在线程 1 中,代码打印出“Finished!”这更没有意义,因为至少lfence在 x86 中是“无用”/无操作.

也许我的信息来源不准确或者我误解了某些东西。请帮忙,谢谢!


重要的不是栅栏的运行时效果,而是强制编译器重新加载内容的编译时效果。

Your t1循环不包含volatile读取或任何其他可以与另一个线程同步的内容,因此不能保证它会ever注意任何变量的任何变化。即,当 JIT 到 asm 中时,编译器可以创建一个循环,将值加载到寄存器中一次,而不是每次都从内存中重新加载。这是您始终希望编译器能够对非共享数据进行的优化,这就是为什么该语言具有允许它在不可能同步时执行此操作的规则。

当然,条件可以被提升到循环之外。所以没有任何障碍或任何东西,您的阅读器循环可以 JIT 到实现此逻辑的 asm 中:

if(t.flag) {
   for(;;){}  // infinite loop
}

除了排序之外,Java 的其他部分volatile是假设其他线程可能会异步更改它,因此不能假设多次读取给出相同的值。

But unsafe.loadFence();使 JVM 重新加载t.flag每次迭代都来自(缓存一致性)内存。我不知道这是否是 Java 规范所要求的,或者仅仅是使其能够工作的实现细节。

如果这是带有非atomic变量(这在 C++ 中是未定义的行为),您会在 GCC 等编译器中看到完全相同的效果。_mm_lfence也将是一个编译时完全障碍以及发出无用的lfence指令,有效地告诉编译器所有内存可能已更改,因此需要重新加载。因此它无法重新排序其上的负载,或将它们提升出循环。

顺便说一句,我不太确定unsafe.loadFence()甚至 JITlfencex86 上的指令。它is对于内存排序来说毫无用处(除了非常模糊的东西,比如从 WC 内存中隔离 NT 加载,例如从视频 RAM 进行复制,JVM 可以假设这不会发生),因此 x86 的 JVM JITing 可以将其视为编译时障碍。就像 C++ 编译器所做的那样std::atomic_thread_fence(std::memory_order_acquire);- 阻止编译时跨屏障重新排序负载,但不发出 asm 指令,因为运行 JVM 的主机的 asm 内存已经足够强大。


在线程 2 中,unsafe.fullFence();我认为没用。它只是让that线程等待,直到早期的存储变得全局可见,然后才能发生任何后续的加载/存储。t.flag = false;是一个明显的副作用,无法通过优化消除,因此无论后面是否有障碍,它都肯定会发生在 JITed asm 中,即使它不是volatile。而且它不能被延迟或与其他东西合并,因为同一线程中没有其他东西。

Asm 存储始终对其他线程可见,唯一的问题是当前线程是否等待其存储缓冲区耗尽,然后再在此线程中执行更多操作(尤其是加载)。即阻止所有重新排序,包括 StoreLoad。爪哇volatile这样做,就像 C++ 一样memory_order_seq_cst(通过在每个存储之后使用完整的屏障),但如果没有屏障,它仍然是像 C++ 一样的存储memory_order_relaxed。 (或者当 JITing x86 asm 时,加载/存储实际上与获取/释放一样强大。)

缓存是一致的,并且存储缓冲区总是尽可能快地耗尽自身(提交给 L1d 缓存),以便为更多存储的执行腾出空间。


警告:我对Java了解不多,而且我不知道分配一个非-到底有多么不安全/未定义。volatile在一个线程中读取它并在另一个线程中读取它,而无需同步。根据您所看到的行为,这听起来与您在 C++ 中看到的与非非相同的事情完全相同atomic变量(启用优化,就像 HotSpot 总是做的那样)

(根据 @Margaret 的评论,我更新了一些关于我假设 Java 同步如何工作的猜测。如果我有任何错误陈述,请编辑或评论。)

在非 C++ 数据竞争中atomic变量总是未定义的行为,但是当然,当为真正的 ISA(不进行硬件竞争预防)进行编译时,结果有时是人们想要的。


PS: 仅仅使用屏障来强制编译器重新读取值通常并不安全:即使源将其复制到局部变量,它也可以选择多次重新读取该值。因此,同一个 tmp var 在一次执行中可能看起来既是 true 又是 false。至少在 C 和 C++ 中是这样,因为数据竞争在这些语言中是未定义的行为。看谁害怕一个糟糕的优化编译器?在 LWN 上,如果您只使用屏障和普通(非volatile)变量。再说一次,我不知道这是否是 Java 中可能存在的问题,或者语言规范是否会禁止 JVM 在int tmp = shared_plain_int; if tmp在函数调用中多次使用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 Unsafe.fullFence() 不能确保我的示例中的可见性? 的相关文章

  • 在 portlet 中设置 SearchContainer 以使用 EL 和 JSTL 在 JSP 中使用它

    我正在尝试使用SearchContainer在我的liferay应用程序中 目前我必须使用 JSP Scriplets 来设置results in
  • Java Swing 应用程序消息对话框帮助

    我正在开发 Java Swing 应用程序 我需要创建一个如图所示的对话框 我不知道这个的名字 我无法解释 所以我附上一张照片 请告诉我这叫什么以及如何在我的 GUI 应用程序中创建它 给猫剥皮的方法不止一种 public final cl
  • Java:while循环冻结程序

    我正在制作一个游戏 我需要每 3 秒更新一次 JProgressBar 为此 我使用 while 循环 问题是我的程序由于 while 循环而冻结 我在其他问题中读到它 他们没有帮助我解决这个问题 我不知道如何解决 这是我的代码 publi
  • 如何将完整的日期格式拆分为日期和时间?

    我有很多格式为我的示例所示的字符串 我必须解析它们 我正在尝试确定今天是哪根弦 我的问题是 时间快到了 我只需要比较那个日期 接下来我想检查时间是否在 after 和 before 的两个时间戳 HH mm ss 之间 但存在问题 日期几乎
  • 模式更新后 jOOQ 生成的类的运行时验证?

    我用org jooq util DefaultGenerator在构建过程中生成 jOOQ 类来表示我的数据库模式 当应用程序运行时 架构预计会在应用程序不知情的情况下发生更改 此类更改可能与已生成的代码兼容 也可能不兼容 如何在运行时检测
  • 在 json 中解析尾随字符

    我正在尝试检查 json 是否有效 并且我遇到了奇怪的行为 当我将一些字符附加到可解析的 json 时 jackson 和 gson 都会解析它 并且它们会忽略尾随字符 我想检查 json 是否严格有效 请帮忙 我尝试了几个标志mapper
  • 始终等待页面加载到 PageObjects 上

    因此 当出现问题时 我只是创建了一个简单的 selenium JBehave 代码 我将首先发布简化的代码 然后稍后解释我的问题是什么 所以这里我们有一个简单的 AbstractClass 它将在我的 PageObjects 上继承 此类仅
  • 如何将日期字符串解析为Date? [复制]

    这个问题在这里已经有答案了 如何将下面的日期字符串解析为Date object String target Thu Sep 28 20 29 30 JST 2000 DateFormat df new SimpleDateFormat E
  • org.openqa.selenium.NoSuchSessionException:会话 ID 为空。调用 quit() 后使用 WebDriver?

    我已经进行了一些搜索 但仍然遇到同样的问题 我相信这可能是由于我的网络驱动程序是静态的造成的 我不太确定 在我的主课中 我包括了 BeforeTest and AfterTest BeforeTest包括根据我的 XML 文件启动新浏览器
  • Android:TelephonyManager 类

    我不明白为什么 API 文档中这么写TelephonyManager类是public 但是当我尝试创建一个实例时 它说它不是公共类 并且无法从包中访问 我看到它也说使用Context getSystemService Context TEL
  • JTree ConvertValueToText 返回在更改时被截断

    我有一个自定义树实现convertValueToText 此实现取决于某些全局状态 如果返回的字符串比先前返回的字符串更长 实际上我认为更宽 因为以像素为单位触发它 则文本将被截断并用 填充 当重绘是由 取消 选择元素或某个元素引起时 情况
  • 在 libgdx 中渲染 box2d

    我有一个使用 FitViewport 的大小为 800x480 的游戏世界 并且最初使用像素渲染 box2d 实体 固定装置 因此所有物理效果都显得浮动且缓慢 查看文档后 我意识到 box2d 使用度量单位 因此我将 box2d 位置和大小
  • 如何在java中从包含.0的浮点数中删除小数部分

    我只想删除包含的浮点数的小数部分 0 所有其他数字都是可以接受的 例如 I P 1 0 2 2 88 0 3 56666 4 1 45 00 99 560 O P 1 2 2 88 3 567 4 1 45 99 560 有什么方法可以做到
  • 在 java 8 下使用泛型出现类型错误,但在 java 7 下则不然

    我有一段代码可以在 java 7 下编译良好 但不能在 java 8 下编译 这是一个独立的重现示例 我已经采用了显示此问题的真实代码并删除了所有实现 import java util Iterator class ASTNode
  • java中永远不会出现的异常

    我为点和向量编写一个类 我想用它们来计算向量的点和范数 这些是点类和向量类 public class Point public float x y public class MyVector public Point start end 我
  • IntelliJ - 无效源版本:17

    我已经在 IntelliJ 中使用 Gradle 创建了一个使用 Java 17 的新 Java 项目 运行我的应用程序时出现错误Cause error invalid source release 17 我的设置 我已经安装了openjd
  • JavaFX Integer Spinner (IntegerSpinnerValueFactory) 不会将值回绕到最小值

    我创建了一个带有值的整数微调器 min 5 max 15 and initialValue 12 and wrapAround true 一旦旋转器到达max 15 增量期间的值 而不是将值重置为min 5 正如它所说文档 https op
  • 对于双核手机,availableProcessors() 返回 1

    我最近购买了一部 Moto Atrix 2 手机 当我尝试查看手机中的处理器规格时 Runtime getRuntime availableProcessors 返回 1 proc cpuinfo 也仅包含有关处理器 0 的信息 出于好奇
  • Android - 从渲染线程内结束活动

    下午好 我不熟悉 android 中的活动生命周期 并且一直在尽可能地阅读 但我不知道如何以良好的方式解决以下问题 我有一个使用 GLSurfaceView 的活动来在屏幕上绘制各种内容 在这个 GLSurfaceView 的渲染线程中 我
  • java中什么是静态接口?

    我正在阅读Map Entry界面 当我注意到它是一个static界面 我不太明白什么是静态接口 它与常规接口有什么不同 public static interface Map Entry

随机推荐