volatile是一种轻量且在有限的条件下线程安全技术,它保证修饰的变量的可见性和有序性,但非原子性。相对于synchronize高效,而常常跟synchronize配合使用。同时voilate关键字在JDK中的大量使用,例如在单例模式中的double check的对变量使用的voilate等,同时该关键字在面试的中比例非常高,因此本博文详细的介绍Volatile底层原理,帮助大家更加深入的回答面试官的问题。
Java内存模型是java虚拟机规范中所定义的一种内存模型,Java内存模型是标准化的,屏蔽掉了底层不同计算机的区别。(JMM与JVM的内存模型不是一个,大家一定好好的理解!)
现代计算机系统都加入一层读写速度尽可能接近处理器运算速度的高速缓存(cache)
来作为内存与处理器之间的缓冲。将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,因为它引入了一个新的问题:缓存一致性(CacheCoherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存(MainMemory)。
Java内存模型(JavaMemoryModel)
描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量,存储到内存和从内存中读取变量这样的底层细节。
所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
。不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。
- 操作use之前必须先执行read和load操作。
- 操作assign之后必须执行store和write操作。
由特性性保证了read、load和use的操作连续性,assign、store和write的操作连续性,从而达到工作内存读取前必须刷新主存最新值;工作内存写入后必须同步到主存中。读取的连续性和写入的连续性,看上去像线程直接操作了主存。
lock和unlock操作并不直接开放给用户使用,而是提供给像Synchronize关键字指定monitorenter和monitorexit隐式使用。关于Synchronize的监听器锁monitor,javac编译后会在作用的方法前后增加monitorenter和monitorexit指令,详细的可以查看Synchronize原理。
package com.zhuangxiaoyan.java.base.javabase.VolatileTest;
/**
* @Classname VolatileVisibility
* @Description TODO
* @Date 2022/5/15 18:40
* @Created by xjl
*/
public class VolatileVisibility {
public static class TestData {
volatile int num = 0;
public void updateNum() {
num = 1;
}
}
public static void main(String[] args) {
final TestData testData = new TestData();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ChildThread num-->" + testData.num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
testData.updateNum();
System.out.println("ChildThread update num-->" + testData.num);
}
}).start();
while (testData.num == 0) {
}
System.out.println("MainThread num-->" + testData.num);
}
}
(1)TestData
中的num不添加volatile
关键字,System.out.println("MainThread num-->"+testData.num);
这一句一直不会执行。表示while中的条件testData.num == 0一直为0,子线程修改了num对主线程不起作用。
(2)TestData
中的num添加volatile
关键字,System.out.println("MainThread num-->"+testData.num);
会执行,结果如下。
use和assign这两个操作整体上不是一个连续性的原子操作。
volatile本身并不对数据运算处理维持原子性,强调的是读写及时影响主存。
volatile修饰num,num++;num = num+1;这种就是非原子性操作。
非原子性操作
- 主存读取num的值;
- 进行num++运算;
- 将num值写到主存。
像种操作在多线程环境中,use和assign是多次出现,如果有两个线程中读取到主存的num都是2,且同时执行num++,两个线程的结果都是3,这样就产生了脏数据,再写入主存中都是3。核心num++运算并没保证先后顺序执行。为了保证执行运算的线程顺序,可以选择Synchronize。
package com.zhuangxiaoyan.java.base.javabase.VolatileTest;
/**
* @Classname ValatileAtomic
* @Description TODO
* @Date 2022/5/15 18:46
* @Created by xjl
*/
public class ValatileAtomic {
public static class TestData {
volatile int num = 0;
//synchronized
public void updateNum() {
num++;
}
}
public static void main(String[] args) {
final TestData testData = new TestData();
for (int i = 1; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?