Java内存模型&volatile关键字

java内存模型(JMM):java

相关概念:
	1)在命令式编程中,线程之间的通讯机制有两种:共享内存和消息传递。
	2)java的并发采用的是共享内存模型:经过读/写内存中的公共状态进行隐式通讯。

概念:java线程之间的通讯是由java内存模型控制的,JMM决定一个线程对共享变量的写入什么时候对另外一个线程可见。

说明:
	1>线程之间的共享变量存储在主内存中,每一个线程都有一个私有的工做内存,工做内存中存储了该线程读/写共享变量的副本。
	2>工做内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其余的硬件和编译器优化。
	3>线程对变量的全部操做(读取、赋值等)都必须在工做内存中进行,而不能直接读写主内存中的变量。
	4>不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递均须要经过主内存来完成。

volatile关键字:编程

相关概念:
	缓存行:缓存器中能够分配的最小存储单位。
	L1缓存:内部缓存。
	L2缓存:外部缓存。

原理:
	1)为了提升处理速度,处理器不直接和内存进行通讯,而是先将系统内存中的数据读到缓存(L一、L2)后再进行操做,但操做完成后,处理器是不知道什么时候要把操做后的数据写回到内存中。
	2)对volatile修饰的变量进行写操做时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行(即JMM中的工做内存)的数据写回到系统内存中,而且将其它CPU里缓存了该内存地址的数据无效。
	补充:
		1>对volatile修饰的变量进行写操做(赋值)时,在JIT编译器生成的汇编指令中,咱们会发现有一个以Lock为前缀的指令。
		2>以Lock为前缀的指令在多核处理器下会引起了两件事情:①将当前处理器缓存行的数据写回到系统内存中 ②这个写回内存的操做会致使其它CPU里缓存了该内存地址的数据无效。
	
	volatile的内存原语:
		当读一个volatile变量时,JMM会把该线程对应的工做内存置为无效,线程接下来将从主内存中读取共享变量。
		当写一个volatile变量时,JMM会把该线程对应的工做内存中的共享变量值刷新到主内存。
	即:
		1将本地内存中的数据设置为无效,  
		2从主内存中将数据复制到本地内存中,  
		3在本地内存中进行操做,  
		4操做完成后将本地内存中的数据刷新到主内存中。总体看起来就像是直接在主内存中操做同样。  
		
	
说明:
	用volatile修饰的变量若是被一个线程更改了,那么其它的线程都会当即感知,而且每一个线程获取该变量的值都是最新的值,访问volatile修饰的变量看起来就像是直接在内存中读写同样。
	
特性:
	可见性:对一个volatile变量的读,(任意线程)老是能看到对这个volatile变量最后的写入。
	原子性:对一个volatile变量的读/写具备原子性,但相似于volatile++这种复合操做不具备原子性。			

优势:
	不会引发线程上下文的切换

	
volatile与synchronized的比较:
	1)关键字volatile只能修饰变量,synchronized能够修饰代码块、方法
	2)volatile不能保证原子性,synchronized保证原子性:
		volatile能够保证数据的可见性,可是不能保证原子性,因此volatile解决的是变量在多线程之间的可见性;
		synchronized能够保证原子性,也保证了可见性(synchronized会将私有内存和公共内存中的数据作同步),因此synchronized解决的是多线程之间访问资源的同步性。

重排序:缓存

说明:在执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。

重排序分2种类型:

	1)编译器重排序:编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。
	2)处理器重排序:
		1>指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。
		2>内存系统的重排序:  因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。


JMM如何实现volatile写/读的内存语义:

	1)JMM针对编译器制定的volatile重排序规则:
	
		两个操做间重排序的条件:
			1>当第一个操做是volatile读,无论第二个操做是什么,都不能重排序。这个规则确保volatile读以后的操做不会被编译器重排序到volatile读以前。
			2>当第二个操做是volatile写,无论第一个操做是什么,都不能重排序。这个规则确保volatile写以前的操做不会被编译器重排序到volatile写以后。
			3>当第一个操做是volatile写,第二个操做是volatile读时,不能重排序。
		
			由以上3点能够得出结论:两个volatile变量操做不可以进行重排序。
		
	2)为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。(内存屏障:将前面操做的共享变量值刷新到主内存中。)
	
		
		
	
参考资料:java并发编程的艺术
相关文章
相关标签/搜索