并发

【Java 并发笔记】volatile 相关整理

本文中其他相关概念可看【Java并发笔记】并发机制底层实现整理文章中说明。1.hsdis通过hsdis可以查看Java编译后的机器指令。下载后拷贝至$JAVA_HOME/jre/lib/amd64/server目录下。下载后拷贝至$JAVA_HOME\jre\bin\server目录下。window32/64位,可以下载hsdis-amd64.dll/hsdis-i386.dll或者linxu下载

1. hsdis

  • 通过 hsdis 可以查看 Java 编译后的机器指令。

  • 拷贝完成后,运行 java -XX:+UnlockDiagnosticVMOptions。

    • 提示 Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled,则没有加载成功,检查文件名称是否正确。

    • 或者设置 export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server/

    • 安装成功后提示。

......
    -agentpath:<pathname>[=<options>]
                  load native agent library by full pathname    -javaagent:<jarpath>[=<options>]
                  load Java programming language agent, see java.lang.instrument    -splash:<imagepath>
                  show splash screen with specified image
See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details.  0x00007fd34511a20f: mov    %rsp,%rdx  0x00007fd34511a212: and    $0xfffffffffffffff0,%rsp  0x00007fd34511a216: callq  0x00007fd35a935a40  ;   {runtime_call}
  0x00007fd34511a21b: hlt    
[Deopt Handler Code]
  0x00007fd34511a21c: mov    $0x7fd34511a21c,%r10  ;   {section_word}

  0x00007fd34511a226: push   %r10  0x00007fd34511a228: jmpq   0x00007fd345047240  ;   {runtime_call}
  0x00007fd34511a22d: hlt    
  0x00007fd34511a22e: hlt    
  0x00007fd34511a22f: hlt
  • 可以运行以下命令生成日志。

    • Test 为 class 文件名称。

java -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:LogFile=jit.log -XX:-BackgroundCompilation -XX:+PrintAssembly Test

2. volatile

  • volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。

2.1 volatile 的特性

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)

  • 禁止进行指令重排序。(实现有序性)

  • volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。

2.2 volatile 的实现原理

2.2.1 volatile 可见性实现

  • volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现。

    • 内存屏障,又称内存栅栏,是一个 CPU 指令。

    • 在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序。

  • 写一段简单的 Java 代码,声明一个 volatile 变量,并赋值。

public class Test {
    private volatile int a;
    public void update() {
        a = 1;
    }
    public static void main(String[] args) {
        Test test = new Test();
        test.update();
    }}
  • 通过 hsdis 和 jitwatch 工具可以得到编译后的汇编代码。

......
  0x0000000002951563: and    $0xffffffffffffff87,%rdi  0x0000000002951567: je     0x00000000029515f8
  0x000000000295156d: test   $0x7,%rdi  0x0000000002951574: jne    0x00000000029515bd
  0x0000000002951576: test   $0x300,%rdi  0x000000000295157d: jne    0x000000000295159c
  0x000000000295157f: and    $0x37f,%rax  0x0000000002951586: mov    %rax,%rdi  0x0000000002951589: or     %r15,%rdi  0x000000000295158c: lock cmpxchg %rdi,(%rdx)  //在 volatile 修饰的共享变量进行写操作的时候会多出 lock 前缀的

原创不易,完成人机校验,阅读全文

相关推荐