了解Java中的易失性和非易失性读/写 [英] Understanding volatile and non-volatile read/write in Java

查看:209
本文介绍了了解Java中的易失性和非易失性读/写的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的java代码如下

public class MyClass {
    volatile int voltile ;  //7
    int nonVoltile ;   //8

    public static void main(String[] args) {
        for(int i=1; i<100000; i++){
            f();
        }
    }

    static void f(){   //16
        MyClass t = new MyClass();  //17
        t.voltile = t.nonVoltile;  //18

        t.nonVoltile = 0x11111; //20
        t.voltile = 0x22222;  //21

        t.nonVoltile = t.nonVoltile + 1; //23
        t.voltile = t.voltile + 1; //24

    }
}

生成的汇编片段函数f如下

Generated assembly snippets for function "f" is as follows

对于易失性写入

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)

我有以下问题


  1. 为什么两个上面有说明?为什么不直接mov 0xc(%esi)到0x8(%esi)

  2. 0x024c7405有什么用:lock addl $ 0x0,(%esp)?

易于阅读

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)

只有一条读指令生成


  1. 这个易失性读取是否发生在主内存中?

  2. 为什么没有像 lock addl 指令锁那样用于易失性写入?

  1. Is this volatile read happening from main memory?
  2. Why nothing like lock addl instruction lock is used which was there for volatile write?

如我所知,我也粘贴整个程序集

CompilerOracle: print *MyClass.f
Compiled method (c1)     142   14   !         j.assembly.MyClass::f (81 bytes)
 total in heap  [0x024c72c8,0x024c77d8] = 1296
 relocation     [0x024c7398,0x024c73bc] = 36
 main code      [0x024c73c0,0x024c7600] = 576
 stub code      [0x024c7600,0x024c7620] = 32
 oops           [0x024c7620,0x024c7624] = 4
 metadata       [0x024c7624,0x024c7628] = 4
 scopes data    [0x024c7628,0x024c76a4] = 124
 scopes pcs     [0x024c76a4,0x024c77d4] = 304
 dependencies   [0x024c77d4,0x024c77d8] = 4
Loaded disassembler from hsdis-i386.dll
Decoding compiled method 0x024c72c8:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x14830304} 'f' '()V' in 'j/assembly/MyClass'
  #           [sp+0x40]  (sp of caller)
  0x024c73c0: mov    %eax,0xffffc000(%esp)
  0x024c73c7: push   %ebp
  0x024c73c8: sub    $0x38,%esp
  0x024c73cb: mov    $0x14830350,%edx   ;   {metadata('j/assembly/MyClass')}
  0x024c73d0: mov    %fs:0x0,%ecx
  0x024c73d8: mov    0xfffffff4(%ecx),%ecx
  0x024c73db: mov    0x34(%ecx),%eax
  0x024c73de: lea    0x10(%eax),%edi
  0x024c73e1: cmp    0x3c(%ecx),%edi
  0x024c73e4: ja     0x024c7589
  0x024c73ea: mov    %edi,0x34(%ecx)
  0x024c73ed: mov    0x60(%edx),%ecx
  0x024c73f0: mov    %ecx,(%eax)
  0x024c73f2: mov    %edx,0x4(%eax)
  0x024c73f5: xor    %ecx,%ecx
  0x024c73f7: mov    %ecx,0x8(%eax)
  0x024c73fa: mov    %ecx,0xc(%eax)
  0x024c73fd: mov    %eax,%esi          ;*new  ; - j.assembly.MyClass::f@0 (line 17)

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)

  0x024c740a: movl   $0x11111,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@19 (line 20)

  0x024c7411: mov    $0x22222,%eax
  0x024c7416: mov    %eax,0x8(%esi)
  0x024c7419: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@25 (line 21)

  0x024c741e: movl   $0x11112,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@35 (line 23)

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)

  0x024c7428: inc    %eax
  0x024c7429: mov    %eax,0x8(%esi)
  0x024c742c: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@45 (line 24)

  0x024c7431: lea    0x20(%esp),%edi
  0x024c7435: mov    %esi,0x4(%edi)
  0x024c7438: mov    (%esi),%eax
  0x024c743a: mov    %eax,%ebx
  0x024c743c: and    $0x7,%ebx
  0x024c743f: cmp    $0x5,%ebx
  0x024c7442: jne    0x024c74ca
  0x024c7448: mov    %eax,(%edi)
  0x024c744a: mov    0x4(%esi),%ebx
  0x024c744d: mov    0x60(%ebx),%ebx
  0x024c7450: xor    %eax,%ebx
  0x024c7452: mov    %fs:0x0,%eax
  0x024c745a: mov    0xfffffff4(%eax),%eax
  0x024c745d: xor    %ebx,%eax
  0x024c745f: and    $0xffffff87,%eax
  0x024c7462: je     0x024c74eb
  0x024c7468: test   $0x7,%eax
  0x024c746d: jne    0x024c74be
  0x024c746f: test   $0x180,%eax
  0x024c7474: jne    0x024c749a
  0x024c7476: mov    (%edi),%eax
  0x024c7478: and    $0x1ff,%eax
  0x024c747e: mov    %fs:0x0,%ebx
  0x024c7486: mov    0xfffffff4(%ebx),%ebx
  0x024c7489: or     %eax,%ebx
  0x024c748b: lock cmpxchg %ebx,(%esi)
  0x024c748f: jne    0x024c7595
  0x024c7495: jmp    0x024c74eb
  0x024c749a: mov    0x4(%esi),%ebx
  0x024c749d: mov    0x60(%ebx),%ebx
  0x024c74a0: mov    %fs:0x0,%eax
  0x024c74a8: mov    0xfffffff4(%eax),%eax
  0x024c74ab: or     %eax,%ebx
  0x024c74ad: mov    (%edi),%eax
  0x024c74af: lock cmpxchg %ebx,(%esi)
  0x024c74b3: jne    0x024c7595
  0x024c74b9: jmp    0x024c74eb
  0x024c74be: mov    (%edi),%eax
  0x024c74c0: mov    0x4(%esi),%ebx
  0x024c74c3: mov    0x60(%ebx),%ebx
  0x024c74c6: lock cmpxchg %ebx,(%esi)
  0x024c74ca: mov    (%esi),%eax
  0x024c74cc: or     $0x1,%eax
  0x024c74cf: mov    %eax,(%edi)
  0x024c74d1: lock cmpxchg %edi,(%esi)
  0x024c74d5: je     0x024c74eb
  0x024c74db: sub    %esp,%eax
  0x024c74dd: and    $0xfffff003,%eax
  0x024c74e3: mov    %eax,(%edi)
  0x024c74e5: jne    0x024c7595         ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)

  0x024c74eb: mov    0xc(%esi),%eax     ;*getfield nonVoltile
                                        ; - j.assembly.MyClass::f@54 (line 27)

  0x024c74ee: inc    %eax
  0x024c74ef: mov    %eax,0xc(%esi)     ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@59 (line 27)

  0x024c74f2: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@64 (line 28)

  0x024c74f5: inc    %eax
  0x024c74f6: mov    %eax,0x8(%esi)
  0x024c74f9: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@69 (line 28)

  0x024c74fe: lea    0x20(%esp),%eax
  0x024c7502: mov    0x4(%eax),%edi
  0x024c7505: mov    (%edi),%esi
  0x024c7507: and    $0x7,%esi
  0x024c750a: cmp    $0x5,%esi
  0x024c750d: je     0x024c7527
  0x024c7513: mov    (%eax),%esi
  0x024c7515: test   %esi,%esi
  0x024c7517: je     0x024c7527
  0x024c751d: lock cmpxchg %esi,(%edi)
  0x024c7521: jne    0x024c75a6         ;*monitorexit
                                        ; - j.assembly.MyClass::f@73 (line 26)

  0x024c7527: add    $0x38,%esp
  0x024c752a: pop    %ebp
  0x024c752b: test   %eax,0xdd0100      ;   {poll_return}
  0x024c7531: ret                       ;*return
                                        ; - j.assembly.MyClass::f@80 (line 30)

  0x024c7532: mov    %fs:0x0,%esi
  0x024c753a: mov    0xfffffff4(%esi),%esi
  0x024c753d: mov    0x1a4(%esi),%eax
  0x024c7543: movl   $0x0,0x1a4(%esi)
  0x024c754d: movl   $0x0,0x1a8(%esi)
  0x024c7557: mov    %eax,%esi
  0x024c7559: lea    0x20(%esp),%eax
  0x024c755d: mov    0x4(%eax),%ebx
  0x024c7560: mov    (%ebx),%edi
  0x024c7562: and    $0x7,%edi
  0x024c7565: cmp    $0x5,%edi
  0x024c7568: je     0x024c7582
  0x024c756e: mov    (%eax),%edi
  0x024c7570: test   %edi,%edi
  0x024c7572: je     0x024c7582
  0x024c7578: lock cmpxchg %edi,(%ebx)
  0x024c757c: jne    0x024c75b7         ;*monitorexit
                                        ; - j.assembly.MyClass::f@78 (line 26)

  0x024c7582: mov    %esi,%eax
  0x024c7584: jmp    0x024c75ec
  0x024c7589: mov    %edx,%edx
  0x024c758b: call   0x024bc740         ; OopMap{off=464}
                                        ;*new  ; - j.assembly.MyClass::f@0 (line 17)
                                        ;   {runtime_call}
  0x024c7590: jmp    0x024c73fd
  0x024c7595: mov    %esi,0x4(%esp)
  0x024c7599: mov    %edi,(%esp)
  0x024c759c: call   0x024bdc40         ; OopMap{esi=Oop [36]=Oop off=481}
                                        ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)
                                        ;   {runtime_call}
  0x024c75a1: jmp    0x024c74eb
  0x024c75a6: lea    0x20(%esp),%eax
  0x024c75aa: mov    %eax,(%esp)
  0x024c75ad: call   0x024bde00         ;   {runtime_call}
  0x024c75b2: jmp    0x024c7527
  0x024c75b7: lea    0x20(%esp),%eax
  0x024c75bb: mov    %eax,(%esp)
  0x024c75be: call   0x024bde00         ;   {runtime_call}
  0x024c75c3: jmp    0x024c7582
  0x024c75c5: nop    
  0x024c75c6: nop    
  0x024c75c7: mov    %fs:0x0,%esi
  0x024c75cf: mov    0xfffffff4(%esi),%esi
  0x024c75d2: mov    0x1a4(%esi),%eax
  0x024c75d8: movl   $0x0,0x1a4(%esi)
  0x024c75e2: movl   $0x0,0x1a8(%esi)
  0x024c75ec: add    $0x38,%esp
  0x024c75ef: pop    %ebp
  0x024c75f0: jmp    0x024bbec0         ;   {runtime_call}
  0x024c75f5: hlt    
  0x024c75f6: hlt    
  0x024c75f7: hlt    
  0x024c75f8: hlt    
  0x024c75f9: hlt    
  0x024c75fa: hlt    
  0x024c75fb: hlt    
  0x024c75fc: hlt    
  0x024c75fd: hlt    
  0x024c75fe: hlt    
  0x024c75ff: hlt    
[Exception Handler]
[Stub Code]
  0x024c7600: call   0x024bd6c0         ;   {no_reloc}
  0x024c7605: push   $0x77f387fc        ;   {external_word}
  0x024c760a: call   0x024c760f
  0x024c760f: pusha  
  0x024c7610: call   0x77e22130         ;   {runtime_call}
  0x024c7615: hlt    
[Deopt Handler Code]
  0x024c7616: push   $0x24c7616         ;   {section_word}
  0x024c761b: jmp    0x0245c2b0         ;   {runtime_call}
OopMapSet contains 2 OopMaps

#0 
OopMap{off=464}
#1 
OopMap{esi=Oop [36]=Oop off=481}
Picked up _JAVA_OPTIONS: -Djava.net.preferIPv4Stack=true
Java HotSpot(TM) Client VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output


推荐答案

你看到的是什么生成的反汇编是Java volatile 字段的特性。

主题不能简单解释,因为它源于CPU的演变和工作方式,但是根据说明简单地说:

What you see in the generated disassembly is a peculiarity of Java volatile fields.
The topic cannot be explained briefly because it arises from the the way CPUs evolved and works, but simply put, given the instructions:

 THREAD 0                       THREAD 1
 int x = 0, y = 0;              while (y==0);
 x = 1;                         int z = x, w = y;
 y = x + 1;

THREAD 1 很可能以 z结尾= 0 w = 2

这不是您通常想要的。为了更好地理解为什么会发生这种情况,请参考这个伟大的博客这篇精彩的文字

为了解决这个问题,Java定义了一个内存模型,您可以在 Java语言规范

在该模型中,它定义了一个名为发生在之前的指令之间的关系,这种关系确保了发生在另一条指令之前的指令的每个副作用对于后者。

简单地说,这保证你没有像上面的例子那样令人讨厌的惊喜,其中,在两个不同的线程中,指令tions x = 1 int z = x 不在发生之前关系中并且无法保证对 x 的写入对另一个线程是可见的(但是保证它对 THREAD 0 可见)。

To address this problem Java define a Memory Model, you can read more about it in the chapter 17.4 of Java Language Specification.
In that model it is defined a relationship between instructions called happen-before and such relationship ensures that every side-effect of an instruction that happen-before another instruction is visible to the latter.
Simply put this guarantees that you have no nasty surprise like in the example above, where, being in two different thread, the instructions x = 1 and int z = x are not in an happen-before relationship and there is no guarantee that the write to x is visible to another thread (there is however the guarantee that it is visible to THREAD 0).

第17.4章列出了发生在之前的两条指令的关系,例如在同一个线程中。

在这种关系中生成两个指令的另一个条件是进入另一种关系: synchronize-with 关系。

Java volatile 字段在写入和从该字段读取之间生成 synchronize-with 。这是一个非常强大的条件!

简单地说: volatile - > 同步 - - > 发生 - 之前 - >副作用的序列化。

The chapter 17.4 lists what makes two instructions in a happen-before relationship, for example being in the same thread.
Another condition that make two instructions in such a relationship is being into another kind of relationship: the synchronize-with relationship.
Java volatile field generate a synchronize-with between a write to and a read from that field. This is a very strong condition!
Simply put: volatile -> synchronize-with -> happen-before -> serialization of side effects.

所以Java volatile 是一种可以经常用来代替锁的同步机制。

So Java volatile is a mechanism of synchronization that can be often used instead of a lock.

为了满足线程之间发生之前的关系,有必要使用内存屏障

In order to satisfy the happen-before relationship between threads it is necessary to use a memory barrier.

您编译了IA32体系结构的Java代码,a.k.a。x86。这种架构中有各种围栏指令专用于微调。并且有一些原始的序列化指令:这些指令可以保证之前的所有其他指令,包括它们的副作用,在序列化之前完成。

它们是穷人的障碍。

You compiled your Java code for IA32 architecture, a.k.a. x86. There are various fence instructions in such architecture that are dedicated to fine tuning. And there are raw serializing instructions: these are instructions that guarantee that every other instruction before, including their side effects, are completed before the serializing one is completed.
They are the barriers of the poor man.

  lock addl $0x0,(%esp)

正如您所看到的,此指令不执行任何操作,它在 esp 处向DWORD添加零,但它使用 lock 前缀,正在序列化,因此充当内存屏障。说实话,是一个沉重的人。

As you can see this instruction do nothing, it adds zero to the DWORD at esp, but it does it using the lock prefix, which is serializing, therefore acting as a memory barrier. An heavy one to be honest.

您的其他问题只是回答:

The other questions of your are simply to answer:


  • 有两个因为你正在读取 nonVoltile (第一步)并写入 voltile (第二步)。

  • 易失性读取不需要障碍,因为每次写入使用的 voltile 序列化写入和读取操作。

  • 如果不考虑缓存层次结构,则会从主内存中发生易失性读取。请注意,您只考虑增加该字段的三条指令中的第一条。

  • There are two moves because you are reading from nonVoltile (first move) and writing to voltile (second move).
  • The volatile read don't need a barrier because the one used by every write to voltile serializes both write and read operations.
  • The volatile read happens from the main memory if you don't consider the cache hierarchy. Note that you are considering just the first of the three instructions that increment the field.

这篇关于了解Java中的易失性和非易失性读/写的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆