Java并发杂谈(一):volatile的底层原理,从字节码到CPU
volatile的特性
volatile是Java中用于修饰变量的关键字,其主要是保证了该变量的可见性以及顺序性,但是没有保证原子性;其是Java中最为轻量级的同步关键字;
接下来我将会一步步来分析volatile关键字是如何在Java代码层面、字节码层面、JVM源码层次、汇编层面、操作系统层面、CPU层面来保证可见性和顺序性的;
Java代码层面
当一个变量被定义为volatile之后,具备两项特性:
- 保证此变量对所有线程的可见性
- 禁止指令重排序优化
volatile所保证的可见性
volatile所修饰的变量在一条线程修改一个变量的值的时候,新值对于其他线程来说是可以立即知道的;
普通变量的值在线程间传递的时候都是通过主内存去完成;
根据JMM我们可以知道,每一个线程其实都有它单独的栈空间,而实际的对象其实都是存放在主内存中的,所以如果是普通对象的话,便会有一个栈空间的对象与主内存中的对象存在差异的时间;而volatile所修饰的变量其保持了可见性,其会强制让栈空间所存在的对应变量失效,然后从主内存强制刷新到栈空间,如此便每次看到的都是最新的数据;
volatile所保证的禁止指令重排
Java的每一行语句其实都对应了一行或者多行字节码语句,而每一行字节码语句又对应了一行或者多行汇编语句,而每一行汇编语句又对应了一行或者多行机器指令;但是CPU的指令优化器可能会对其指令顺序进行重排,优化其运行效率,但是这样也可能会导致并发问题;而volatile便可以强制禁止优化指令重排;
volatile在字节码层面的运用
我们先看到以下代码
点击查看代码
public class Main {
static int a ;
static volatile int b ;
public static synchronized void change(int num) {
num = 0;
}
public static void main(String[] args) {
a = 10;
b = 20;
change(a);
}
}
我们先试用javac来将java文件编译为class文件,然后通过javap -v来反编译;
点击查看代码
Classfile /opt/software/java-study/Main.class
Last modified Mar 1, 2022; size 400 bytes
MD5 checksum c7691713c9365588495a60da768c32a6
Compiled from "Main.java"
public class Main
SourceFile: "Main.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#21 // Main.a:I
#3 = Fieldref #5.#22 // Main.b:I
#4 = Methodref #5.#23 // Main.change:(I)V
#5 = Class #24 // Main
#6 = Class #25 // java/lang/Object
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 b
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 change
#15 = Utf8 (I)V
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 SourceFile
#19 = Utf8 Main.java
#20 = NameAndType #10:#11 // "<init>":()V
#21 = NameAndType #7:#8 // a:I
#22 = NameAndType #9:#8 // b:I
#23 = NameAndType #14:#15 // change:(I)V
#24 = Utf8 Main
#25 = Utf8 java/lang/Object
{
static int a;
flags: ACC_STATIC
static volatile int b;
flags: ACC_STATIC, ACC_VOLATILE
public Main();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static synchronized void change(int);
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=1, args_size=1
0: iconst_0
1: istore_0
2: return
LineNumberTable:
line 5: 0
line 6: 2
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: bipush 10
2: putstatic #2 // Field a:I
5: bipush 20
7: putstatic #3 // Field b:I
10: getstatic #2 // Field a:I
13: invokestatic #4 // Method change:(I)V
16: return
LineNumberTable:
line 9: 0
line 10: 5
line 11: 10
line 12: 16
}
我们仔细观察加了volatile修饰的变量与其他变量的区别便可以看出,其主要是在flags中添加了一个**ACC_VOLATILE**;同时先进行**putstatic**指令;
volatile在JVM源码方面的运用
在JVM源码方面,我编译了OpenJDK7然后利用find与grep进行全局查找,然后进行方法追踪,由于涉及到大量C++的知识,我便跳过其C++代码追踪,而直接看最后追踪到的函数;
先来做一个总结,其实volatile的JVM源码的原理对应的是被称为内存屏障来实现的;
点击查看代码
static void loadload();
static void storestore();
static void loadstore();
static void storeload();
这四个分别对应了经常在书中看到的JSR规范中的读写屏障
- LoadLoad屏障:(指令Load1; LoadLoad; Load2),在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- LoadStore屏障:(指令Load1; LoadStore; Store2),在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:(指令Store1; StoreStore; Store2),在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- StoreLoad屏障:(指令Store1; StoreLoad; Load2),在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
对于volatile操作而言,其操作步骤如下:
- 每个volatile写入之前,插入一个StoreStore,写入以后插入一个StoreLoad
- 每个volatile读取之前,插入一个LoadLoad,读取之后插入一个LoadStore
在JVM源码层次而言,内存屏障直接起到了禁止指令重排的作用,且之后与总线锁或者MESI协议配合实现了可见性;
汇编层次
在汇编层次而言,我是使用JITWatch配合hsdis进行的转汇编,可以发现在含有volatile的变量的时候,汇编指令会有一个lock前缀,而lock前缀在CPU层次中自己实现了内存屏障的功能;
CPU层次
在x86的架构中,含有lock前缀的指令拥有两种方法实现;
一种是开销很大的总线锁,它会把对应的总线直接全部锁住,如此明显是不合理的;
所以后期intel引入了缓存锁以及mesi协议,如此便可以轻量化的实现内存屏障;
Java并发杂谈(一):volatile的底层原理,从字节码到CPU的更多相关文章
- 【死磕Java并发】-----深入分析volatile的实现原理
通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它 ...
- 【死磕Java并发】—–深入分析volatile的实现原理
通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它比使 ...
- Java 并发编程:volatile的使用及其原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:Synchronized及其实现原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- (转)Java并发编程:volatile关键字解析
转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...
- Java并发编程:volatile关键字解析(转载)
转自https://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 ...
- Java并发编程:volatile关键字解析-转
Java并发编程:volatile关键字解析 转自海子:https://www.cnblogs.com/dayanjing/p/9954562.html volatile这个关键字可能很多朋友都听说过 ...
- 6、Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- 转:Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字, ...
随机推荐
- XXE题型记录
XXE题型记录 [CSAWQual 2019]Web_Unagi 题解 打开题目,点开upload中的例子发现是上传xml文件 根据about中的提示Flag is located at /flag, ...
- C# TCP传输文件示例代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 将Cesium Tools用于更好的构建管理
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ Cesium技术正在给建筑业带来革命性的变化.我们与 partn ...
- Cesium入门7 - Adding Terrain - 添加地形
Cesium入门7 - Adding Terrain - 添加地形 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com ...
- echarts的通用属性的介绍
通常做数据可视化时,会用到统计图,这里我使用的是Echarts,对于第一次用的人来说,还是有点难度的,主要是里面的属性太多,看的头痛,这里我自己做个笔记 这里的配置项手册里面就是查找各种属性了,在Ec ...
- CSS八种让人眼前一亮的HOVER效果
一.发送效果 HTML <div id="send-btn"> <button> // 这里是一个svg的占位 Send </button> & ...
- linux不使用useradd添加新用户
不使用useradd创建新的用户 1.进入用户特征信息:/etc/passwd 编辑: vim /etc/passwd 命令模式 :G 进入末行 进入编辑模式 :在最后添加新用户信息: 例:new_u ...
- BIMFACE 二次开发 SDK 之歌
<BIMFACE SDK 之歌>讲述了作者与 BIMFACE 从相识.相知.相爱.相守的艳遇之爱唯美故事 我是一个小小的程序员 穿行在人来人往的IT行业之间 编程工作与建筑信息化相关 ...
- Net6 DI源码分析Part2 Engine,ServiceProvider
ServiceProvider ServiceProvider是对IServiceProvider实现,它有一个internal的访问修饰符描述的构造,并需要两个参数IServiceCollectio ...
- Java线程--ForkJoinPool使用
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871099.html Java线程--ForkJoinPool使用 简单解释下: Fork是 ...