volatile的作用

volatile的作用

1.它能保证多线程时共享变量的可见性。
2.禁止指令重排序

首先要想保证线程安全,就必须保证两个必要条件互斥访问和可见性
互斥访问:同一时间只能保证一个线程访问某一资源。
可见性:在多线程环境中,一个线程修改了某个共享资源,对于其他线程来说可见

所以可以知道volatile不一定能保证线程安全

首先了解一下java内存模型(JMM)
在JMM中,所有的变量都放在了主存中,每个线程都都有自己的工作内存,并且线程只能处理自己的工作内存,线程所有的操作都是在工作内存中完成的。
所以整个流程就很清楚了:线程执行时,首先将主存中的数据加载到自己的工作内存,然后对数据副本进行操作,再将操作后的数据返回给主存,主存的数据得以更新。

但是出于性能的考虑,操作后的数据不一定会立即更新到主存,所以此时其他线程进行读操作就会读到脏数据。
看下面的例子

public class demo {
	int a=1;
	Boolean flag=false;
	
	public void write() {
		a=2;               //1
		flag=true;        //2
	}
	
	public void read() {
		if(flag)        //3
		{
			int res=a*a;     //4
		}
	}
}

假如线程1先执行write方法,线程2先执行read方法,因为线程1将flag修改为true,但是没有将数据更新到主存,所以if里面内容不会执行。而a的值也没有传回主存,所以即使执行结果可能为1。

volatile是怎么保证可见性的?
当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的工作内存设为无效

指令重排
为了提高并行度,编译器和处理器可能对指令进行重排,但是会遵守as-if-serial语义。as-if-serial语义指的是不管怎么重排序,最终的执行结果不会改变!
注:仅限单线程情况下,多线程和复合语句不能保证。
比如
a=1;//1
b=2;//2
c=a+b;//3
语句1和语句2可能被重排序,但是一定在语句3之前执行
再看上面这个例子

public class demo {
	int a=1;
	Boolean flag=false;
	
	public void write() {
		a=2;//1
		flag=true;//2
	}
	
	public void read() {
		if(flag)//3
		{
			int res=a*a;//4
		}
	}
}

如果语句1和语句2进行了重排序,线程1执行write,线程2执行read,但执行完语句2时,线程1被挂起,那么线程2执行read得到的结果就会是1,而不是4.。

所有当使用volatile修饰flag变量时,它保证了可见性和禁止重排序,所以就能避免这两个问题,当a被修改后,会立刻更新到主存,线程2会拿到最新的值。

最后说一下禁止指令重排序,指的是只影响操作volatile修饰的变量的语句,不会被重排。但它之前的语句后之后的语句是否重排不做保证!
比如
a=1;//语句1
b=2;//语句2
c=1;//被volatile修饰 语句3
b=1;//语句4
a=2;//语句 5
语句1,2的顺序和语句4,5的顺序不会被保证。但它能保证在执行到语句3时,语句1,2已经执行完,并且对语句3,4,5可见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值