java并发volatile的使用

关键字volatile可以说是java虚拟机提供的最轻量级的同步机制。

当一个变量定义为volatile之后,它就具备两种特性:

1、保证此变量对所有线程的可见性,即当一个线程修改了这个变量的值,新值对于其他线程是立即可得的。

注:关于volatile的可见性经常被开发人员误解,觉得:volatile变量对所有线程是立即可见的,所以volatile变量在各个线程中是一致的,所以基于volatile变量的运算在并发 下是安全的。volatile变量在各个线程的工作内存中不存在不一致的问题(在各个线程的工

作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要 先刷新,执行引擎看不到不一致的情况,因此可以认为不存在不一致的问题) 但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。如程序1-1

public class VolatileTest {
	public static volatile int race = 0;
	
	public static void increase() {
		race++;
	}
	
	private static final int THREAD_COUNT = 20;
	
	public static void main(String[] args) {
		Thread[] threads = new Thread[THREAD_COUNT];
		for(int i=0;i<THREAD_COUNT;i++) {
			threads[i] = new Thread(new Runnable(){
				@Override
				public void run() {
					// TODO Auto-generated method stub
					for(int i=0;i<10000;i++) {
						increase();
					}
				}
			});
			threads[i].start();
		}
		while(Thread.activeCount() > 1) {
			Thread.yield();
		}
		System.out.println(race);
	}
}
程序1-1


如果并发正确,结果应该为200000,但结果是一个小鱼200000的数字。

问题在于race++我们用javap反编译之后,得到程序1-2


程序1-2


可以看出,race++操作分程了4条指令,首先getstatic将race的值取到操作栈顶,volatile保证了race的值此时是正确的,但是执行iconst_1,iadd这些指令的时候,其他线程可能已经把race值加大了,栈顶的值此时已过期,所以putstatic执行后就可能把较小的race值同步回主内存之中。

在不符合一下两条规则的运算场景中,我们仍然要通过加锁来保证原子性

1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值

2、变量不需要与其他的状态变量共同参与不变约束



2、第二个语义是禁止指令重排序优化

Map configOptions;
	char[] configText;
	volatile boolean initialized = false;
	
	class thread1 implements Runnable {
		@Override
		public void run() {
			configOptions = new HashMap<>();
			//模拟读取配置信息,当读取完成后将initialized设置为true以通知其他线程配置可用
			configText = readConfigureFile();
			initialized = true;
		}
	}
	
	class thread2 implements Runnable{
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(!initialized) {
				//initialized为false的时候睡眠
				Thread.sleep();
			}
			//使用初始化好的配置信息
			doSomethingWithConfig();
		}
	}
	
	//模拟读取配置信息方法
	private char[] readConfigureFile() {
		//读取配置信息
		return null;
	}
	//模拟使用配置信息的方法
	private void doSomethingWithConfig() {
		//使用初始化好的配置信息
	}
程序2-1


在程序2-1中的语义为执行完线程thread1中的读取配置文件操作后,将initialized状态设置为true,线程thread2判断initialized状态为true的时候,对读取到的配置文件进行处理。而这时候如果没有对initialized变量添加volatile修饰,就可能会由于指令重排序的优化,导致位于thread1中最后一句的initialized=true被提前执行,这样thread2中使用配置信息的代码就可能出现错误,而volatile关键字则可以避免此类情况的发生。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值