Java源码分析系列笔记-3.volatile
1. 是什么
Java 的轻量级锁,主要保证了有序性、可见性和一定的原子性
- 轻量级
相比于 synchronized,volatile 不会引起上下文切换(不会造成线程阻塞) - 原子性
对任意单个 volatile 变量的读/写具有原子性,但类似于 volatile++这种复合操作不具有原子性 - 可见性
volatile 写会把数据同时写入主内存,并让其他线程对这个数据的工作内存失效,这样其他线程读的时候就需要去主内存中读取 - 有序性
对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入。
2. 什么情况 volatile 比 synchronized 更合适
2.1. 例子
如下程序。thread1 并不会停止
public class VolatileTest
{
private static boolean isRunning = true;
public static void main(String[] args) throws InterruptedException
{
Thread thread1 = new Thread(()->{
System.out.println("thread1 is running");
while (isRunning)
{
}
System.out.println("thread1 will be stopped");
});
thread1.start();
Thread.sleep(1000);
Thread thread2 = new Thread(()->{
System.out.println("thread2 is running");
isRunning = false;
System.out.println("thread2 change isRunning flag");
});
thread2.start();
thread1.join();
thread2.join();
}
}
2.2. 无法停止的原因分析

- Thread1 从主内存把 isRunning 这个变量加载到工作内存中,值为 true 所以一直运行
- Thread2 从主内存把 isRunning 这个变量加载到工作内存中,值为 true 改为 false,写回工作内存,再写回主内存
- Thread1 一直从工作内存中读取这个变量,一直为 true,所以还是无法停止运行
2.3. 解决方法
将 isRunning 使用 volatile 修饰
public class VolatileTest
{
private static volatile boolean isRunning = true;
public static void main(String[] args) throws InterruptedException
{
Thread thread1 = new Thread(()->{
System.out.println("thread1 is running");
while (isRunning)
{
}
System.out.println("thread1 will be stopped");
});
thread1.start();
Thread.sleep(1000);
Thread thread2 = new Thread(()->{
System.out.println("thread2 is running");
isRunning = false;
System.out.println("thread2 change isRunning flag");
});
thread2.start();
thread1.join();
thread2.join();
}
}
2.4. volatile vs synchronized
| volatile | synchronized | |
|---|---|---|
| 内存模型三性 | 可见性、有序性 | 可见性、有序性、原子性 |
| 是否造成线程阻塞【重量级别】 | 不会 | 会 |
| 应用范围 | 变量级别 | 变量、方法、类级别 |
3. 汇编源码实验
3.1. 下载编译 hsdis-amd64.dll
参考How to build hsdis-amd64.dll and hsdis-i386.dll on Windows或者hsdis-amd64.7z
3.2. 放入 JRE bin 目录下

3.3. 对比实验
- 有 volatile
public class TestVolatile
{
private static volatile int i = 0;
public static void main(String[] args)
{
test();
}
private static void test()
{
i++;
}
}
- 没有 volatile
public class TestVolatile
{
private static int i = 0;
public static void main(String[] args)
{
test();
}
private static void test()
{
i++;
}
}
3.4. 加上 jvm 参数运行
-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-Inline -XX:CompileCommand=print,*TestVolatile.test
使用 IDEA 的话如下图:

3.5. 输出结果对比
结果如附件:
- volatile.txt
- 普通.txt
使用 BeyondCompare 对比图如下:
![]()
4. 根据实验结果分析原理
从汇编语言层面看,有 volatile 的结果比没有 volatile 的多了一个指令:lock addl $0x0,(%rsp) ,这条指令起到内存屏障的作用
禁止屏障两边的指令重排序
强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效
4.1. 可见性
根据内存屏障的作用 2 可以实现可见性,表现如下
- volatile 写会把数据同时写入主内存,并让其他线程对这个数据的工作内存失效
- 其他线程 volatile 读的时候就需要去主内存中读取
4.2. 有序性
根据内存屏障的作用 1 可以实现有序性,表现如下

在 volatile 写之前插入释放屏障【LoadStore+StoreStore】使得该屏障之前的任何读写操作都先于这个 volatile 写被提交;
在 volatile 读之后插入获取屏障【LoadLoad+LoadStore】使得这个 volatile 读操作先于该屏障之后的任何读写操作被提交。
5. 参考
- 深入理解 Java 内存模型(四)——volatile-InfoQ
- 再有人问你 volatile 是什么,把这篇文章也发给他。-HollisChuang's Blog
- Java volatile 关键字底层实现原理解析 - 王泽远的博客 | Crow's Blog
- 精确解释 java 的 volatile 之可见性、原子性、有序性(通过汇编语言) - tantexian 的博客空间 - OSCHINA
- volatile 与 synchronized 的区别 - 掘金
Java源码分析系列笔记-3.volatile的更多相关文章
- Java源码分析系列之HttpServletRequest源码分析
从源码当中 我们可以 得知,HttpServletRequest其实 实际上 并 不是一个类,它只是一个标准,一个 接口而已,它的 父类是ServletRequest. 认证方式 public int ...
- Java源码分析系列
1) 深入Java集合学习系列:HashMap的实现原理 2) 深入Java集合学习系列:LinkedHashMap的实现原理 3) 深入Java集合学习系列:HashSet的实现原理 4) 深入Ja ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- spring源码分析系列 (2) spring拓展接口BeanPostProcessor
Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...
- MyCat源码分析系列之——BufferPool与缓存机制
更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...
- MyCat源码分析系列之——配置信息和启动流程
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- MyBatis 源码分析系列文章导读
1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...
- spring源码分析系列 (8) FactoryBean工厂类机制
更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...
- spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析
更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...
随机推荐
- nginx下增加https端口的方法
一.进入根目录我是使用xshell进行远程连接服务器的,连接到服务器首先输入cd /进入到根目录在这里插入图片描述二.配置nginx.conf文件首先输入cd etc/nginx进入到nginx目录在 ...
- Delphi 判断字符是否是汉字
function IsHZ(ch: WideChar): boolean; var i: Integer; begin i := Ord(ch); if (i < 19968) or (i &g ...
- linux命令提示符高亮
说明 \033 或 \e :两者是等价的,表示转义字符(ASCII escape character),即键盘左上角的ESC键.033是ESC的八进制ASCII码.注意,在"老式" ...
- AIR780E引脚复用笔记
1.应用场景: 使用AIR780E模块驱动TM1637数码管驱动芯片,原有方案是AIR724UG+TM1637.为了降低成本,按照官方方案进行代码迁移. 伴随着代码迁移,硬件引脚也需要做相应调 ...
- 事务注解@Transactional
目录 1.属性介绍 2.传播机制 准备例子 总结 3.原理 4.失效场景 一.属性介绍 1.isolation 属性 事务的隔离级别,默认值为 Isolation.DEFAULT.可选的值有: Iso ...
- python,url请求失败重新请求的方法(try、except 应用)
爬虫请求链接,有时候会出现请求失败或者等待时间很长的情况,用下面的方法可以一定程度的解决这个问题 url='https://cl.xxxx.xyz/'+url try: response = requ ...
- 从excle中读取数据的方法
倒入两个库:ExcelLibrary,Collections 首先,必须注意文件格式为xls 表格内容 open_Excel C:\\Users\\Beckham\\Desktop\\a.xls ...
- eolinker请求参数:提交参数JSON转换格式不正确的解决方法
当某个接口的提交参数类型为"array"时,该接口被自动化测试调用会转换成text类型. 导致执行测试的时候,整个参数转化json格式不正确 解决方法是在 格式不正确的项后面 配 ...
- 为什么在 MySQL 中不推荐使用多表 JOIN?
为什么在 MySQL 中不推荐使用多表 JOIN? 在 MySQL 中,虽然 JOIN 操作是关系型数据库的重要特性,用于从多个表中获取数据,但在某些场景下不推荐频繁使用多表 JOIN.以下是一些主要 ...
- 记录一次SpringBoot + Vue前后分离项目的部署流程
前言 本教程使用黑马 SpringBoot3+Vue3全套视频教程 大事件项目作为前后端代码. 前置需要: mysql jdk redis nginx linux环境 打包 前端 构建项目命令 npm ...
