草庐IT

java - 64 位 OpenJDK 7/8 中并发长写入的值完整性保证

coder 2024-03-07 原文

注意:此问题与 volatile、AtomicLong 或所描述用例中的任何感知缺陷无关。

我要证明或排除的性质如下:

Given the following:

  • a recent 64-bit OpenJDK 7/8 (preferably 7, but 8 also helpful)
  • a multiprocessing Intel-base system
  • a non-volatile long primitive variable
  • multiple unsynchronized mutator threads
  • an unsynchronized observer thread

Is the observer always guaranteed to encounter intact values as written by a mutator thread, or is word tearing a danger?

JLS:不确定

此属性存在于 32 位基元和 64 位对象引用中,但 JLS 不保证 long 和 double:

17.7. Non-atomic Treatment of double and long:
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

但别急:

[...] For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts. Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. [...]

因此,JLS 允许 JVM 实现拆分 64 位写入,并鼓励开发人员进行相应调整,同时也鼓励 JVM 实现者坚持使用 64 位写入。我们还没有最新版本的 HotSpot 的答案。

HotSpot JIT:谨慎乐观

由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,因此我尝试分析 JIT 编译的实际程序集输出。长话短说:需要进一步测试,但我只能看到 long 上的原子 64 位操作。

我用了hdis ,一个 OpenJDK 的反汇编插件。 在我老化的 OpenJDK 7u25 构建中构建并安装插件后,我开始编写一个简短的程序:

public class Counter {
  static long counter = 0;
  public static void main(String[] _) {
    for (long i = (long)1e12; i < (long)1e12 + 1e5; i++)
      put(i);
    System.out.println(counter);
  }

  static void put(long v) {
    counter += v;
  }
}

我确保始终使用大于 MAX_INT 的值(1e12 到 1e12+1e5),并重复该操作足够次数 (1e5) 以触发 JIT。

编译后,我用 hdis 执行了 Counter.main(),如下所示:

java -XX:+UnlockDiagnosticVMOptions \ 
     -XX:PrintAssemblyOptions=intel \
     -XX:CompileCommand=print,Counter.put \ 
     Counter

JIT 为 Counter.put() 生成的程序集如下(为方便起见添加了十进制行号):

01   # {method} 'put' '(J)V' in 'Counter'
02 ⇒ # parm0:    rsi:rsi   = long
03   #           [sp+0x20]  (sp of caller)
04   0x00007fdf61061800: sub    rsp,0x18
05   0x00007fdf61061807: mov    QWORD PTR [rsp+0x10],rbp  ;*synchronization entry
06                                                 ; - Counter::put@-1 (line 15)
07   0x00007fdf6106180c: movabs r10,0x7d6655660    ;   {oop(a 'java/lang/Class' = 'Counter')}
08 ⇒ 0x00007fdf61061816: add    QWORD PTR [r10+0x70],rsi  ;*putstatic counter
09                                                 ; - Counter::put@5 (line 15)
10   0x00007fdf6106181a: add    rsp,0x10
11   0x00007fdf6106181e: pop    rbp
12   0x00007fdf6106181f: test   DWORD PTR [rip+0xbc297db],eax        # 0x00007fdf6cc8b000
13                                                 ;   {poll_return}

有趣的行用'⇒'标记。 如您所见,加法运算是使用 64 位寄存器 ( rsi) 在四字(64 位)上执行的。

我还尝试通过在“长计数器”之前添加一个字节类型的填充变量来查看字节对齐是否是一个问题。汇编输出的唯一区别是:

之前

    0x00007fdf6106180c: movabs r10,0x7d6655660    ;   {oop(a 'java/lang/Class' = 'Counter')}

之后

    0x00007fdf6106180c: movabs r10,0x7d6655668    ;   {oop(a 'java/lang/Class' = 'Counter')}

两个地址都是 64 位对齐的,那些“movabs r10, ...”调用使用的是 64 位寄存器。

到目前为止,我只测试了加法。我假设减法的行为类似。
其他操作,例如按位操作、赋值、乘法等仍有待测试(或由足够熟悉 HotSpot 内部的人确认)。

解释器:不确定

这给我们留下了非 JIT 场景。让我们反编译 Compiler.class:

$ javap -c Counter
[...]
static void put(long);
Code:
   0: getstatic     #8                  // Field counter:J
   3: lload_0
   4: ladd
   5: putstatic     #8                  // Field counter:J
   8: return
[...]

...我们会对第 7 行的“ladd”字节码指令感兴趣。 但是,我一直无法 trace it through到目前为止,特定于平台的实现。

感谢您的帮助!

最佳答案

事实上,您已经回答了自己的问题。

在 64 位 HotSpot JVM 上没有对 doublelong 的“非原子处理”,因为

  1. HotSpot 使用 64 位寄存器来存储 64 位值(x86_64.adx86_32.ad)。
  2. HotSpot 在 64 位边界上对齐 64 位字段 ( universe.inline.hpp )

关于java - 64 位 OpenJDK 7/8 中并发长写入的值完整性保证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25173208/

有关java - 64 位 OpenJDK 7/8 中并发长写入的值完整性保证的更多相关文章

  1. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

  2. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  3. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  4. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  5. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  10. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

随机推荐