volatile解释

java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
解释:多线程环境下,读取和更改可能是同时进行,因此具有不确定性,而volatile关键字保证了共享变量的“可见性”。可见性就是当一个线程修改一个共享变量是,另外一个线程能读到这个修改的值。
如何能够保证可见性,我们先看下cpu的一些术语。
1.缓冲行:cpu高速缓存中可以分配的最小储存单元。
2.原子操作:不可终端的一系列操作。
3.写命中:处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓冲行中,如果在,处理器将这个操作数写回到缓存,而不是写回到内存,称之为写命中。
通过编译器编译出的汇编代码,我们可以看到加了volatile的程序多了一段代码:
lock add1 &0X0,(%esp);
我们查看手册可知lock前缀的指令在多核处理器下会引发两件事情:
1.将当前处理器缓存行的数据写回系统内存;
2令其他cpu里缓存了该内存地址的数据无效;

为何要让cpu如此工作?因为内存中读取是消耗资源的,为了提高处理速度,处理器不直接和内存进行通信,而是将系统内存的数据读到内部缓存,而且不知道何时会写入内存。
你的线程会有自己独立的堆栈,堆等,来存储着来自共享变量的值,也就是拷贝了一份!那你修改了内存的值是不会影响到线程拷贝的资源的。
那不加voliate的变量自然是缓存中的数据了,其他线程修改该线程的资源也是在线程内存中修改,不清楚何时会写入内存。证明如下:

public class TestThread implements Runnable{
	private  boolean flag;
	public TestThread() {
		flag = true;
		new Thread(this).start();
	}
	@Override
	public void run() {
		int i=1;
		while(flag) {
			i++;
		}
		System.out.println(flag);
		System.out.println("我的线程代码要执行结束啦");
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

这是一个很普通的线程,在这无限循环等待另一个线程修改自身的flag来结束线程。

public class MainThread {
	public static void main(String[] args) {
		TestThread testThread = new TestThread();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
		}
		testThread.setFlag(false);
		System.out.println("我已经修改了线程的flag了");
	}
}

主线程,启动子线程后两秒修改子线程对象的flag。
运行
在这里插入图片描述
子线程在两秒后并未结束,这就说明我们的控制变量没有发生改变,原因就是没有添加voliate导致这个变量没有可见性,另一线程对flag的写操作对其他线程的读操作是不可见的。(就是上面说的拷贝)
加上voliate关键字
在这里插入图片描述
我们要的结果,这就说明了不加voliate的线程局部变量是从缓存中读取的,主线程修改了内存,却没有修改到缓存。
voliate关键字令每次读取都会发出lock原语,将缓存中的数据写回内存,并且另其他的缓存中有改变量地址的元素都无效化,也就是还会从内存中读取至线程内存块之中。
也就保证了该元素的可见性。

附加问题:我们取消掉voliate在线程的循环里加输出语句。

public class TestThread implements Runnable{
	private  boolean flag;

	public TestThread() {
		flag = true;
		new Thread(this).start();
	}

	@Override
	public void run() {
		int i=1;
		while(flag) {
			i++;
			//System.out.println(i);
			System.out.println(flag);
		}
		System.out.println(flag);
		System.out.println("我的线程代码要执行结束啦");
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}	
}

在这里插入图片描述
达到了预期效果且完成了整个过程后结束了,可见输出语句之中有点意思。
我们看下源码
在这里插入图片描述
这有一个重量级锁,问题解决了。
因为即便是简单的锁模块,锁里什么都没写,依然要进行锁的获取和锁的释放!
当线程释放锁时,JMM(java内存模型)会把该线程对应的本地内存中的共享变量刷新到住内存中去。
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。
其实,线程之间的不可见性不仅仅是因为拷贝,还有重排序,不同线程之间的代码在执行是只要满足顺序一致性内存模型,保证happens——before
规则,代码的执行次序是与程序员编写顺序不同的,关于重排序,小编其他的文章中也有讲解,感谢您的阅读,如有错误,请指出,帮助新手,改善学习氛围。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值