volatile指令重排_volatile可见性和指令重排

volatile关键字的2个作用

1.线程的可见性

2.防止指令重排

什么是线程的可见性?

线程的可见性 就是一个线程对一个变量进行更改操作 其他线程获取会获得最新的值。

线程在执行的行 操作主线程的变量。会将变量的副本拷贝一份到线程的工作区域(避免每次到主线程读取 提高效率),在更改后的一段时间内写入主内存

如下示例代码:

public class Accounting implementsRunnable {boolean quit=false;int i=0;

@Overridepublic voidrun() {while (!quit){

i++;

}

System.out.println("线程退出");

}public static void main(String[] args) throwsInterruptedException {

Accounting accounting= newAccounting();

Thread a1= new Thread(accounting, "a1");

Thread a2= new Thread(newRunnable() {

@Overridepublic voidrun() {try{

Thread.sleep(2000);

System.out.println("开始通知线程结束");

accounting.setQuit(true);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

});

a2.start();

a1.start();

Thread.sleep(1000);

}public booleanisQuit() {returnquit;

}public void setQuit(booleanquit) {this.quit =quit;

}

}

这段代码的逻辑就是线程a1 执行循环操作  a2 2秒后设置quit为true任务结束 打印 "线程退出";

那么真的能够成功退出吗?我们看看 线程执行在内存中的操作图

打印:

开始通知线程结束

a2 线程首先将自己工作线程的quit改为ture ,然后一定时间之后去将主内存的quit改为true  ,但是a1线程始终是操作的是自己的工作内存的副本 所以死循环

这个时候在quit加上volatile关键字

volatile boolean quit=false;

打印

开始通知线程结束

线程退出

加上volatile关键字后。当一个线程对变量进行修改会更新自己的工作内存里面的值,然后立即将改动的值刷新到主内存,同时线程2的工作内存的quit副本缓存失效  下次直接到主内存读取  所以能够正常执行

记录一个小插曲

System.out.println,sychronized,Thread.sleep Thread.sleep 影响可见性?

System.out.println

public class Accounting implementsRunnable {boolean quit=false;int i=0;

@Overridepublic voidrun() {while (!quit){

i++;

System.out.println(i);

}

System.out.println("线程退出");

}public static void main(String[] args) throwsInterruptedException {

Accounting accounting= newAccounting();

Thread a1= new Thread(accounting, "a1");

Thread a2= new Thread(newRunnable() {

@Overridepublic voidrun() {try{

Thread.sleep(2000);

System.out.println("开始通知线程结束");

accounting.setQuit(true);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

});

a2.start();

a1.start();

Thread.sleep(1000);

}public booleanisQuit() {returnquit;

}public void setQuit(booleanquit) {this.quit =quit;

}

会发现没有加上volatile一样可以成功退出 。那我们上面说的 线程的内存处理 不成立了吗?

查资料说 是因为jvm对锁的优化。因为如果我们在循环里面加上sychronize同步锁 会产生大量的锁竞争 所以jvm优化过后

synchronized (this){while (!quit){//.....

}

}

但是我们并没有在while里面加锁啊。我们看看打印的方法源码

public void println(intx) {synchronized (this) {

print(x);

newLine();

}

}

sleep方法并没有加锁,为什么能够保证可见性

sleep是阻塞线程并不释放锁,让出cpu调度。 让出cpu调度后下次执行会刷新工作内存

指令重排

指令重排指在编译的时候,在不单线程运行不影响结果的情况下进行指令优化

如:

public classContext {boolean isLoad=false;

Object configuration=null;public voidloadConfiguration(){

System.out.println("正在加载配置文件");

configuration= newObject();

isLoad=true;

}public voidinitContext(){

System.out.println("正在进行初始化");

}public static voidmain(String[] args) {

Context context=newContext();

context.loadConfiguration();if(context.isLoad){

context.initContext();

}

}

}

这段代码就是先加载配置文件信息  然后初始化上下文

我们在单线程下 把他们的顺序调换模拟指令重排 会对结果没有影响

public voidloadConfiguration(){

isLoad=true;

System.out.println("正在加载配置文件");

configuration= newObject();

}

但是在多线程下面

public classContext {boolean isLoad=false;

Object configuration=null;public voidloadConfiguration(){//模拟jvm指令重排 将isLoad命令排在第一位

isLoad=true;/***

* 模拟并发情况下指令重排。导致的isload=true排到前面。

* 这个时候配置文件没初始化。initContext监听到lsLoad等于true根据配置文件进行初始化*/

try{

Thread.sleep(2000);

}catch(InterruptedException e) {

e.printStackTrace();

}

configuration= newObject();//isLoad=true;指令重排前

}public voidinitContext(){

configuration.toString();

System.out.println("正在进行初始化");

}public static voidmain(String[] args) {

Context context=newContext();//负责监听 如果加载完毕 则进行上下午初始化

Thread t2=new Thread(newRunnable() {

@Overridepublic voidrun() {while (true){if(context.isLoad){

context.initContext();break;

}

}

}

},"t2");//负责加载配置文件

Thread t1=new Thread(newRunnable() {

@Overridepublic voidrun() {

context.loadConfiguration();

}

},"t1");

t1.start();

t2.start();

}

}

只是模拟指令重排 先不考虑可见性  这种情况会初始化context 没有configuration 报错  使用volatile关键字修饰可以避免

值得注意的一点

volatile虽然能够保证线程的可见性 但是并不能保证原子性  比如i++操作 都是读出i的值 进行运算再写入。如果在读出的时候别的线程改变了 就会不一致

哪种场景适合用volatile 对一个变量的值进行修改 不依赖其他值。 比如 index=true   而不是i=i+j;或则index=j>a   或 a=j (会从内存中读出j的值 然后赋值到a);

java提供atomic cas能够性能比锁高能够保证原子性 如:atomicInt atomictDouble

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值