深入理解Java虚拟机(5)Java内存模型

Java内存模型

由于计算机的存储与处理器的运算速度之间有几个数量级的差距,会造成无法充分利用处理器,因此现代计算机加入了一层读写速度尽可能接近处理器运算速度的高速缓存(cache)来作为内存与处理器之间的缓冲;先将运算需要的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就不需要等待缓慢的内存读写。 对于现在的多核机器,有多个CPU,每个CPU有自己的高速缓存,这就需要进行缓存一致性,不然同步回内存时会出现冲突,这里的一致性是靠一致性协议来完成的。如图:
在这里插入图片描述

主内存和工作内存

在这里插入图片描述
如图,主内存和工作内存,主内存就是存储线程共享变量的地方,工作内存与线程对应,是线程私有的,速度快很多,是主内存中变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量,不同线程之间的变量值的传递均需要通过主内存来完成。

内存间的交互操作,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,主要通过以下八个指令来完成(这八个指令均是原子性的):lock、unlock、read、load、use、assign、store、write。

volatile关键字

volatile关键字有两个语义。
语义1:定义为volatile的变量对所有线程可见,即一个线程中修改该值,别的线程可以立即得知。普通变量修改后,别的线程不会立即得知,需要先同步回主内存,别的线程再从主内存中取到这个变量才会拿到新值。

问题:volatile关键字是线程安全的吗?
回答:不是,举个例子,如下代码片段

public class VolatileTest {
	public static volatile int race = 0;
	public static void increase() {
		race++;
	}
	private static final int THREADS_COUNT = 20;
	public static void main(String[] args) {
		 Thread[] threads = new Thread[THREADS_COUNT];
		 for (int i = 0; i < THREADS_COUNT; i++) {
		 	threads[i] = new Thread(new Runnable(){
				@override
				public void run() {
					for(int i = 0; i < 10000; i++){
						increase();
					}
				}
			});
			threads[i].start();
		}
		//等待所有线程运行结束
		while(Thread.activeCount() > 1){
			Thread.yield();
		}
		System.out.println(race);
	}
}	

这段代码发起了20个线程,每个线程对race变量进行10000次自增操作,如果volatile关键字线程安全,则最终会输出200000。但是实际运行后,最终的输出会小于200000,因为volatile关键字虽然让该变量对所有线程可见,但比如,目前线程1执行自增操作时,发现volatile修饰的变量值为10,然后准备在10的基础上进行自增操作,但此时线程2将volatile关键字增加到了11,但线程1还是在10的基础上进行自增,然后线程1将该值增加到11,并同步给了其他线程,这样就使被volatile修饰的这个变量值变小了。
因此volatile关键字不是线程安全的。

语义2:禁止指令重排序优化,这可以防止并发中某些错误的发生。
比如,线程A要初始化配置信息供线程B使用,初始化后将flag标志位true,线程B发现flag为true时读取配置信息,如果不使用volatile关键字,线程A重排序可能会在没有初始化信息的情况下将flag设为true,此时线程B读取配置信息发现没有,因此出错。
用volatile关键字可以禁止指令重排序,原理是加上一个内存屏障,指令重排序无法把后面的指令重排序到内存屏障前面去。

long与double类型的特殊规则

对于long和double类型变量的特殊规则,因为long和double类型都是64位,规定允许虚拟机将没有被volatile关键字修饰的64位数据拆分成两次32位的操作来进行,即允许虚拟机可以选择不保证64位数据类型的load、store、read、write这四个操作的原子性,这样就不会是线程安全的,但是在大多数的虚拟机中还是会选择将这些操作实现为原子性的操作,所以日常编写并发程序时不需要专门对long和double类型的数据进行特殊处理。

synchronized关键字

synchronized关键字表示一个变量一个时刻只允许一条线程对其进行lock操作。类似于操作系统中同一时刻只允许一个线程进入临界区。
该关键字可以修饰变量,对象和类。
修饰变量时表示对该变量,一个时刻只允许一条线程对其进行lock操作。
修饰对象时表示对该对象,一个时刻只允许一条线程对其进行lock操作。
修饰类时表示对于该类的所有对象,静态量等,一个时刻只允许一条线程对其进行lock操作。

另外在此顺便提一下,保证有序性的还有先行发生原则(与时间发生顺序基本无关)。

猜你喜欢

转载自blog.csdn.net/azxucmkvfo/article/details/84647174
今日推荐