简介

Volatile关键字对熟悉java多线程的朋友来说,应该很熟悉了。Volatile是JMM(Java Memory Model)的一个非常重要的关键词。通过是用Volatile可以实现禁止重排序和变量值线程之间可见两个主要特性。

今天我们从汇编的角度来分析一下Volatile关键字到底是怎么工作的。

重排序

这个世界上有两种重排序的方式。

第一种,是在编译器级别的,你写一个java源代码,经过javac编译之后,生成的字节码顺序可能跟源代码的顺序不一致。

第二种,是硬件或者CPU级别的重排序,为了充分利用多核CPU的性能,或者CPU自身的处理架构(比如cache line),可能会对代码进行重排序。比如同时加载两个非互相依赖的字段进行处理,从而提升处理速度。

我们举个例子:

public class TestVolatile {

    private static int int1;
private static int int2;
private static int int3;
private static int int4;
private static int int5; public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++)
{
increase(i);
}
Thread.sleep(1000);
} private static void increase(int i){
int1= i+1;
int2= i+2;
int3= i+3;
int4= i+4;
int5= i+5;
}
}

上面例子中,我们定义了5个int字段,然后在循环中对这些字段进行累加。

先看下javac编译出来的字节码的顺序:

我们可以看到在设置值的过程中是和java源代码的顺序是一致的,是按照int1,int2,int3,int4,int5的顺序一个一个设置的。

然后我们看一下生成的汇编语言代码:

在运行是添加参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:-Inline,或者直接使用JIT Watcher。

从生成的代码中,我们可以看到putstatic是按照int1,int5,int4,int3,int2的顺序进行的,也就是说进行了重排序。

如果我们将int2设置成为Volatile,看看结果如何?

前方高能预警,请小伙伴们做好准备

我们先看putstatic的顺序,从注释里面,我们只发现了putstatic int2, int3和int5。

且慢!我们不是需要设置int1,int2,int3,int4,int5 5个值吗?这里怎么只有3个。

要是没有能独立思考和独立决定的有创造个人,社会的向上发展就不可想像 - 爱因斯坦

这里是反编译的时候注释写错了!

让我们来仔细分析一下汇编代码。

第一个红框,不用懂汇编语言的朋友应该也可以看懂,就是分别给r11d,r8d,r9d,ecx和esi这5个寄存器分别加1,2,3,4,5。

这也分别对应了我们在increase方法中要做的事情。

有了这些寄存器的值,我们再继续往下看,从而可以知道,第二个红框实际上表示的就是putstatic int1,而最后一个红框,表示的就是putstatic int4。

所以,大家一定要学会自己分析代码。

5个putstatic都在,同时因为使用了volatile关键字,所以int2作为一个分界点,不会被重排序。所以int1一定在int2之前,而int3,4,5一定在int2之后。

上图的结果是在JIT Watcher中的C2编译器的结果,如果我们切换到C1编译器:

这次结果没错,5个int都在,同时我们看到这5个int居然没有重排序。

这也说明了不同的编译器可能对重排序的理解程度是不一样的。

写的内存屏障

再来分析一下上面的putstatic int2:

lock addl $0x0,-0x40(%rsp)  ;*putstatic int2 {reexecute=0 rethrow=0 return_oop=0}

这里使用了 lock addl指令,给rsp加了0。 rsp是SP (Stack Pointer) register,也就是栈指针寄存器。

给rsp加0,是不是很奇怪?

加0,虽然没有改变rsp的值,但是因为前面加了lock,所以这个指令会被解析为内存屏障。

这个内存屏障保证了两个事情,第一,不会重排序。第二,所有的变量值都会回写到主内存中,从而在这个指令之后,变量值对其他线程可见。

当然,因为使用lock,可能对性能会有影响。

非lock和LazySet

上面我们提到了volatile会导致生成lock指令。

但有时候,我们只是想阻止重排序,对于变量的可见性并没有那么严格的要求。

这个时候,我们就可以使用Atomic类中的LazySet:

public class TestVolatile2 {

    private static int int1;
private static AtomicInteger int2=new AtomicInteger(0);
private static int int3;
private static int int4;
private static int int5; public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++)
{
increase(i);
}
Thread.sleep(1000);
} private static void increase(int i){
int1= i+1;
int2.lazySet(i+2);
int3= i+3;
int4= i+4;
int5= i+5;
}
}

从结果可以看到,int2没有重排序,也没有添加lock。s

注意,上面的最后一个红框表示的是putstatic int4。

读的性能

最后,我们看下使用volatile关键字对读的性能影响:

public class TestVolatile3 {

    private static volatile int int1=10;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++)
{
readInt(i);
}
Thread.sleep(1000);
} private static void readInt(int i){
if(int1 < 5){
System.out.println(i);
}
}
}

上面的例子中,我们对int1读取10000次。看下编译结果:

从结果可以看出,getstatic int1和不使用volatile关键字,生成的代码是一样的。

所以volatile对读的性能不会产生影响。

总结

本文从汇编语言的角度再次深入探讨了volatile关键字和JMM模型的影响,希望大家能够喜欢。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/jvm-volatile-assembly/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

JVM系列之:从汇编角度分析Volatile的更多相关文章

  1. JVM系列之:从汇编角度分析NullCheck

    目录 简介 一个普通的virtual call 普通方法中的null check 反优化的例子 总结 简介 之前我们在讲Virtual call的时候有提到,virtual call方法会根据传递的参 ...

  2. JVM Java字节码的角度分析switch的实现

    目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...

  3. JVM详解之:汇编角度理解本地变量的生命周期

    目录 简介 本地变量的生命周期 举例说明 优化的原因 总结 简介 java方法中定义的变量,它的生命周期是什么样的呢?是不是一定要等到方法结束,这个创建的对象才会被回收呢? 带着这个问题我们来看一下今 ...

  4. 从汇编角度分析C语言的过程调用

    ➠更多技术干货请戳:听云博客 基本术语定义 1.系统栈(system stack)是一个内存区,位于进程地址空间的末端. 2.在将数据压栈时,栈是自顶向下增长的,该内存区用于函数的局部变量提供内存.它 ...

  5. c语言中函数调用的本质从汇编角度分析

    今天下午写篇博客吧,分析分析c语言中函数调用的本质,首先我们知道c语言中函数的本质就是一段代码,但是给这段代码起了一个名字,这个名字就是他的的这段代码的开始地址 这也是函数名的本质,其实也就是汇编中的 ...

  6. 干货分享丨jvm系列:dump文件深度分析

    摘要:java内存dump是jvm运行时内存的一份快照,利用它可以分析是否存在内存浪费,可以检查内存管理是否合理,当发生OOM的时候,可以找出问题的原因.那么dump文件的内容是什么样的呢? JVM ...

  7. Swift--struct与class的区别(汇编角度底层分析)

    概述 相对Objective-C, Swift使用结构体Struct的比例大大增加了,其中Int, Bool,以及String,Array等底层全部使用Struct来定义!在Swift中结构体不仅可以 ...

  8. jvm系列:Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

  9. jvm系列(九):Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

随机推荐

  1. Python 数字格式转换

    # 数字格式转换v1 '''a = input() a1 = list(a) b = ["零", "一", "二", "三&quo ...

  2. Sql Or NoSql,看完这一篇你就懂了(转五月的仓颉)

    前言 你是否在为系统的数据库来一波大流量就几乎打满CPU,日常CPU居高不下烦恼?你是否在各种NoSql间纠结不定,到底该选用那种最好?今天的你就是昨天的我,这也是写这篇文章的初衷. 这篇文章是我好几 ...

  3. Scala 基础(十四):Scala 模式匹配(二)

    1 匹配数组 1)Array(0) 匹配只有一个元素且为0的数组. 2)Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y.当然可以依次类推Array(x,y,z) 匹配数组有3个元素 ...

  4. 数据分析03 /基于pandas的数据清洗、级联、合并

    数据分析03 /基于pandas的数据清洗.级联.合并 目录 数据分析03 /基于pandas的数据清洗.级联.合并 1. 处理丢失的数据 2. pandas处理空值操作 3. 数据清洗案例 4. 处 ...

  5. 数据可视化之PowerQuery篇(二十)如何计算在职员工数量?

    https://zhuanlan.zhihu.com/p/128652582 ​经常碰到的一类问题是,如何根据起止日期来计算某个时间点的数量,比如: 已知合同的生效日期和到期日期,特定日期的有效合同有 ...

  6. js获取url并截取相应的字段,js解决url获取中文字段乱码问题

    相信url截取信息是一个很常用的小功能页面跳转传参的时候可以在A页面的url挂一些参数到B页面获取正常的页面传参都是以数字和英文为主正常情况下中文获取的时候是有乱码的所谓上有政策下有对策一个正常的ur ...

  7. SqlServer同义词

    最近在项目中遇到跨库操作具有相同表结构的数据表的问题.(A库中的数据表a和B库中的数据表b,两者表结构相同) 跨库操作中我们一般是为了实现不同数据库中表字段信息,字段状态等实时同步,可能很多人会想到使 ...

  8. 初级软件工程师怎么走向BATJ?——献给迷茫中的测试人

    软件测试,邀你同行.你好,我是爱码小哥. 又是一个深夜,打开手机备忘录,想记录一些东西,本人比较静的一个人,所以经常会去 IT行业的贴吧论坛交流一下,逛知乎,论坛,社区你就会发现大量这样的帖子,都会出 ...

  9. Cyber Security - Palo Alto Firewall Objects Addresses, Services, and Groups(2)

    Users Objects and Groups Creating local user objects. Creating local user groups. https://docs.paloa ...

  10. corosync+pacemaker高可用集群

    高可用集群,是指以减少服务中断(如因服务器宕机等引起的服务中断)时间为目的的服务器集群技术.简单的说,集群就是一组计算机,它们作为一个整体向用户提供一组网络资源.这些单个的计算机系统就是集群的节点. ...