想要理解volatile关键字,你只需要掌握它的这三个特点

想要理解volatile关键字,你只需要掌握它的这三个特点

volatile关键字可以保证被修饰变量的可见性

要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:

img

从图中可以看出每一个线程,都有一个本地内存,操作共享变量的时候,线程执行时,会先把主内存中的共享变量拷贝一份到每一个线程的本地内存中,然后再对该共享变量进行操作。等到操作完成之后,在某个时刻会把本地内存中的操作共享变量的结果刷新到主内存中。

能够体现volatile关键字修饰的变量可以被所有的线程看见的例子,如下图:

在这里插入图片描述

上图程序的运行结果,如下图:

在这里插入图片描述

但是如果要是不用volatile关键字修饰标志变量continuePrint,如下图:

在这里插入图片描述

去掉volatile关键字之后的运行结果,如下图:

在这里插入图片描述

去掉volatile关键字之后,Thread-0线程中接收不到main线程中修改标志变量continuePrint后的结果的原因是:main线程和Thread-0线程都有一个本地内存,刚开始的时候,标志变量continuePrint的值是true,这个值存放在主内存中,main线程和Thread-0线程执行的时候,会从主内存把continuePrint=true复制一份到它们的本地内存之中,它们操作各自本地内存之中的continuePrint标志变量的时候互不影响,在main线程中把continuePrint的值改成false,就仅仅是在main线程的本地内存中把continuePrint的值改成了false,不会影响Thread-0线程本地内存中的continuePrint的值,它的值仍然是true,因此Thread-0线程中的while循环不会终止。

加上volatile关键字之后,Thread-0线程中可有接收到main线程中修改结果的原因是:如果用volatile关键字修饰continuePrint标志变量,那么线程main和线程Thread-0在读取continuePrint标志变量的时候,不会从它们的本地内存中读取,而是会从主内存中读取;在读取之后,写入continuePrint标志变量的时候,main线程和Thread-0线程也会直接把continuePrint标志变量的值写入到主内存中,而不是写入到它们的本地内存之中。因此,这样的话,在main线程中把continuePrint标志变量的值改成false之后,在Thread-0线程中是可以读取到的。

volatile不能保证修饰变量的原子性

什么叫做原子性,就是某系列的操作步骤要么全部执行,要么都不执行,比如:

i++这句代码其实对应的JVM字节码指令有三条,第一条是从内存中取出i的值,第二条是对i进行加一,第三条是把加一后的i存入内存中,如果这三条指令不能同时执行,这就叫做不能保证i++这句代码的原子性;

volatile不能保证被修饰的变量的原子性的例子,如下图:

在这里插入图片描述

volatile不能保证被修饰变量的原子性,其实也就是说明了volatile不能保证被修饰变量的线程安全。

volatile可以避免指令重排

什么是指令重排?

你比如有两句代码int a=1;int b=2;指令重排之后可能就变成了int b=2;int a=1;但是指令重排并非可以任意的重排,也是有限制的,比如有三句代码int a=1;int b=a+1;int c=3;因为b依赖a,所以int b=a+1;这句代码就不能排在int a=1;的前面,但是c不依赖a和b的任何一个,所以int c=3这句代码可以重排的任意位置;

指令重排有什么用?

指令重排可以提高cpu处理器的效率,为什么这样说呢?你比如,有两个cpu处理器A,B

处理器A执行代码 变量a++ 变量b++

处理器B执行代码 变量a++ 变量b++

但是如果按这样的顺序执行的话,那么在A处理器执行 变量a++的时候,B处理器就必须先停止,这样也就浪费了cpu资源;

但如果进行一下指令重排,把处理器B执行的代码重排成 变量b++ 变量a++,那么在处理器A执行变量a++操作的时候,处理器B就不需要停止了,它可以执行变量b++操作,这样的话也就提高了cpu的效率。

**volatile关键字修饰的变量不会被指令重排序优化。**这里以《深入理解JAVA虚拟机》中一个例子来说明下自己的理解:

线程A执行的操作如下:

Map configOptions ;
char[] configText;

volatile boolean initialized = false;

//线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
initialized = true;

线程B等待线程A把配置信息初始化成功后,使用配置信息去干活…线程B执行的操作如下:

while(!initialized)
{
    sleep();
}
//使用配置信息干活
doSomethingWithConfig();

如果initialized变量不用 volatile 修饰,在线程A执行的代码中就有可能指令重排序。

即:线程A执行的代码中的最后一行:initialized = true 重排序到了 processConfig方法调用的前面执行了,这就意味着:配置信息还未成功初始化,但是initialized变量已经被设置成true了。那么就导致 线程B的while循环“提前”跳出,拿着一个还未成功初始化的配置信息去干活(doSomethingWithConfig方法)。。。。

因此,initialized 变量就必须得用 volatile修饰。这样,就不会发生指令重排序,也即:只有当配置信息被线程A成功初始化之后,initialized 变量才会初始化为true。综上,volatile 修饰的变量会禁止指令重排序(有序性)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr-X~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值