volatile底层原理详解
今天我们聊聊volatile底层原理;
Java语言规范对于volatile定义如下:
Java编程语言允许线程访问共享变量,为了确保共享变量能够被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。
首先我们从定义开始入手,官方定义比较拗口。通俗来说就是一个字段被volatile修饰,Java的内存模型确保所有的线程看到的这个变量值是一致的,但是它并不能保证多线程的原子操作。这就是所谓的线程可见性。我们要知道他是不能保证原子性的。
内存模型相关概念
Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的修改何时对另外一个线程可见。JMM定义了线程与主内存的抽象关系:线程之间的变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)保存着共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。
如果线程A与线程B通信:
线程A要先把本地内存A中更新过的共享变量刷写到主内存中。
线程B到主内存中读取线程A更新后的共享变量
计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。
有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。
举个例子:
i++;
当线程运行这行代码时,首先会从主内存中读取i,然后复制一份到CPU高速缓存中,接着CPU执行+1的操作,再将+1后的数据写在缓存中,最后一步才是刷新到主内存中。在单线程时没有问题,多线程就有问题了。
如下:假如有两个线程A、B都执行这个操作(i++),按照我们正常的逻辑思维主存中的i值应该=3,但事实是这样么?
分析如下:
两个线程从主存中读取i的值(1)到各自的高速缓存中,然后线程A执行+1操作并将结果写入高速缓存中,最后写入主存中,此时主存i==2,线程B做同样的操作,主存中的i仍然=2。所以最终结果为2并不是3。这种现象就是缓存一致性问题。
解决缓存一致性方案有两种:
通过在总线加LOCK#锁的方式;
通过缓存一致性协议。
但是方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下。
第二种方案,缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。所以JMM就解决这个问题。
volatile实现原理
有volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令,该指令在多核处理器下会引发两件事情。
将当前处理器缓存行数据刷写到系统主内存。
这个刷写回主内存的操作会使其他CPU缓存的该共享变量内存地址的数据无效。
这样就保证了多个处理器的缓存是一致的,对应的处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置无效状态,当处理器对这个数据进行修改操作的时候会重新从主内存中把数据读取到缓存里。
使用场景
volatile经常用于两个场景:状态标记、double check
- 状态标记
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是加上volatile就没问题了。如下所示:
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
volatile boolean inited = false;
//线程1:
context = loadContext();
inited = true;
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
- double check
public class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
客官觉得有用请点赞或收藏,关注公众号JavaStorm,你将发现一个有趣的灵魂!
后面我们继续分析JMM内存模型相关技术。
将自己的知识分享,以后会持续输出,希望给读者朋友们带来帮助。若有帮助读者朋友可以点赞或者关注。
volatile底层原理详解的更多相关文章
- Spring Aop底层原理详解
Spring Aop底层原理详解(来源于csdn:https://blog.csdn.net/baomw)
- 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)
[1]前言 本篇幅是对 线程池底层原理详解与源码分析 的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...
- HBase 底层原理详解(深度好文,建议收藏)
HBase简介 HBase 是一个分布式的.面向列的开源数据库.建立在 HDFS 之上.Hbase的名字的来源是 Hadoop database,即 Hadoop 数据库.HBase 的计算和存储能力 ...
- iptables的概念与底层原理(详解)
目录 一:iptables 1.iptables简介 2.什么是防火墙? 3.防火墙种类 二:iptables基本介绍 1.解析内容 三:iptables流程(讲解) 1.流入本机 2.解析(流入本机 ...
- mysql学习笔记-底层原理详解
前言 我相信每一个程序员都避免不了和数据库打交道,其中Mysql以其轻量.开源成为当下最流行的关系型数据库.Mysql5.0以前以MyISAM作为默认存储引擎,在5.5版本以后,以InnoDB作为默认 ...
- volatile关键字的详解-并发编程的体现
xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!! 参 ...
- 锁之“轻量级锁”原理详解(Lightweight Locking)
大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意. 原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖 ...
- Influxdb原理详解
本文属于<InfluxDB系列教程>文章系列,该系列共包括以下 15 部分: InfluxDB学习之InfluxDB的安装和简介 InfluxDB学习之InfluxDB的基本概念 Infl ...
- JSPatch实现原理详解<二>
本文转载至 http://blog.cnbang.net/tech/2855/ 距离上次写的<JSPatch实现原理详解>有一个月的时间,在这段时间里 JSPatch 在不断地完善和改进, ...
随机推荐
- HTML DOM setTimeout() 方法
转自:http://www.w3school.com.cn/jsref/met_win_settimeout.asp 1.setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. &l ...
- shell入门-awk-3
awk的内置变量 NR 表示行 NF 表示段 显示第十行 [root@wangshaojun ~]# awk -F ':' 'NR==10' 1.txtuucp:x:10:14:uucp:/var/s ...
- struts2 json返回试验
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC "-/ ...
- sql语句去重 最后部分没看 看1 有用
一 数据库 1.常问数据库查询.修改(SQL查询包含筛选查询.聚合查询和链接查询和优化问题,手写SQL语句,例如四个球队比赛,用SQL显示所有比赛组合:举例2:选择重复项,然后去掉重复项:) 数据库里 ...
- 并行fp-growth图解(mahout)
FP-growth Apriori算法的一个主要瓶颈在于,为了获得较长的频繁模式,需要生成大量的候选短频繁模式.FP-Growth算法是针对这个瓶颈提出来的全新的一种算法模式.目前,在数据挖掘领域,A ...
- [MySQL] Data too long for column 'title' at row 1
李刚轻量级JavaEE第六章的坑..艹李刚自己有没试过这些代码的啊,6.4这一份HqlQuery.java里需要的表,根本就跟他提供的sql脚本对不上啊..坑爹啊,而且字符编码集也有问题. 出现这个原 ...
- Umbraco中的RelatedLink的使用
Umbraco中经常需要使用到RelatedLink, 那么在代码中我们如何来获取RelatedLink呢, 可能在Backoffice中我们有一个RelatedLink, 上面有3个链接,如下所示: ...
- Error mounting / dev / sdb1 in Ubuntu
Uncommon users of Ubuntu OS, when connecting USB with NTFS file system, can observe the error: " ...
- 【转】processOnServer
源地址:http://blog.csdn.net/dl020840504/article/details/8856853
- 51nod 1781 Pinball(线段树)
题面 Pinball的游戏界面由m+2行.n列组成.第一行在顶端.一个球会从第一行的某一列出发,开始垂直下落,界面上有一些漏斗,一共有m个漏斗分别放在第2~m+1行,第i个漏斗的作用是把经过第i+1行 ...
