【Java基础】:volatile实现可见性的原理
发布日期:2021-07-17 16:07:35 浏览次数:7 分类:技术文章

本文共 2102 字,大约阅读时间需要 7 分钟。

文章目录

1. 引言

在java并发编程中,一定绕不开volatile、synchronized和lock几个关键字,其中volatile关键字是用来解决共享变量(类成员变量、类的静态成员变量等)的可见性问题的,非共享变量(方法的局部变量)是分配在JVM虚拟机的栈中,是线程私有的,不涉及可见性问题。那么什么是可见性?

2. 什么叫做可见性

可见性:在JAVA规范中是这样定义的:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。通俗的将就是如果有一个共享变量N,当有两个线程T1、T2同时获取了N的值,T1修改N的值,而T2读取N的值。那么可见性规范要求T2读取到的必须是T1修改后的值,而不能在T2读取旧值后T1修改为新值。volatile关键字修饰的共享变量可以提供这种可见性规范,也叫做读写可见。那么底层实现是通过机制保证volatile变量读写可见的?

3. Volatile的实现机制

在说这个问题之前,我们先看看CPU是如何执行java代码的。

在这里插入图片描述
首先编译之后Java代码会被编译成字节码.class文件,在运行时会被加载到JVM中,JVM会将.class转换为具体的CPU执行指令,CPU加载这些指令逐条执行。
在这里插入图片描述
以多核CPU为例(两核),我们知道CPU的速度比内存要快得多,为了弥补这个性能差异,CPU内核都会有自己的高速缓存区,当内核运行的线程执行一段代码时,首先将这段代码的指令集进行缓存行填充到高速缓存,如果非volatil变量当CPU执行修改了此变量之后,会将修改后的值回写到高速缓存,然后再刷新到内存中。如果在刷新会内存之前,由于是共享变量,那么CORE2中的线程执行的代码也用到了这个变量,这是变量的值依然是旧的。volatile关键字就会解决这个问题的,如何解决呢,首先被volatile关键字修饰的共享变量在转换成汇编语言时,会加上一个以lock为前缀的指令,当CPU发现这个指令时,立即做两件事:

  1. 将当前内核高速缓存行的数据立刻回写到内存;
  2. 使在其他内核里缓存了该内存地址的数据无效。

第一步很好理解,第二步如何做到呢?

MESI协议:在早期的CPU中,是通过在总线加LOCK#锁的方式实现的,但这种方式开销太大,所以Intel开发了缓存一致性协议,也就是MESI协议,该解决缓存一致性的思路是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,那么他会发出信号通知其他CPU将该变量的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更改的信号,当发现这个变量的缓存行已经无效时,会从新从内存中读取这个变量。

4. 使用volatile的好处

从底层实现原理我们可以发现,volatile是一种非锁机制,这种机制可以避免锁机制引起的线程上下文切换和调度问题。因此,volatile的执行成本比synchronized更低。

5. volatile的不足

使用volatile关键字,可以保证可见性,但是却不能保证原子操作,例如:

public class TestVolatile {
public volatile int inc = 0; public void increase() {
inc++; } public static void main(String[] args) {
final Test test = new Test(); for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) Thread.yield(); System.out.println(test.inc); }}

这里我们用10个线程,每个线程+1000,预期应该是10000,实际上编译执行这段代码,输出值都会小于10000。为什么会这样?因为,自增操作并不是原子操作,它含括读,加1,写入工作内存三步操作。这三步是分开操作的,当inc的值为5,thead1执行自增操作,当thread1读到5之后,还没有来得及写入就被阻塞了,那么thead2读取的依然是原值。所以volatile的非锁机制只能保证修饰的变量的可见性,而当对变量进行非原子操作时,volatile就无法保证了。这种时候就需要使用synchronzied或lock。

参考

转载地址:https://blog.csdn.net/hxcaifly/article/details/88093099 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【Spring】:事务管理
下一篇:【Java设计模式】:行为型模式—备忘录模式

发表评论

最新留言

逛到本站,mark一下
[***.202.152.39]2024年03月13日 04时35分17秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

php7.1解压包安装,【Swoole】php7.1安装swoole扩展 2021-06-24
linux centos删除安装的包,CentOS yum认为已删除的软件包仍在安装中 2021-06-24
酒店管理系统c语言带注释,酒店管理系统--C语言版.pdf 2021-06-24
c语言 实现sizeof功能,C语言简单实现sizeof功能代码 2021-06-24
c语言sin函数近似值,用泰勒公式求sin(x)的近似值 2021-06-24
c 语言登录系统源代码,c语言源代码---------------个人图书管理系统 2021-06-24
android线程通信方式,Android 主线程和子线程通信问题 2021-06-24
cps1 cps2 android,图文教程:CPS1和CPS2模拟器使用 2021-06-24
在线设计 html5 表单,html5注册表单制作-表单制作-小程序表单制作 2021-06-24
android小闹钟课程设计,《小闹钟》教学设计 2021-06-24
mysql文件系统_MySQL文件系统先睹为快(1) 2019-04-21
nums在python_程序找到一对(i,j),其中nums [i] + nums [j] +(i -j)在Python中最大化?... 2019-04-21
jquery后台内容管理_教育平台项目后台管理系统:课程内容模块 2019-04-21
grouping函数 mysql_sql聚合函数有哪些 2019-04-21
python os.walk如何不遍历隐藏文件_python 获取文件下所有文件或目录os.walk()的实例... 2019-04-21
python 股票估值_【中金固收·固收+】隐藏价值的角落:限售股AAP估值及Python实现方法(上)... 2019-04-21
java文档生成_Java文档自动生成 2019-04-21
java 共享目录_java 操作windows 共享目录方法介绍 2019-04-21
java 监控 宕机_JAVA监测tomcat是否宕机,控制重启 2019-04-21
catch that cow java_POJ3278——Catch That Cow 2019-04-21