多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
本文转载自[volatile关键字及其作用](https://blog.csdn.net/u010255818/article/details/65633033) [TOC] volatile关键字有两个作用,一是保证内存可见性,二是禁止指令重排优化。 # 保证内存可见性 可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。 ## 原理 当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。   volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CP[【源码分析】ThreadPoolExecutor源码分析](ThreadPoolExecutor%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md)U cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。 ![](https://img.kancloud.cn/6b/be/6bbec38ba8bc9f5275b795b2d1061aba_550x429.png) # 禁止指令重排 ## 什么是指令重排 指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。 在JDK1.5之后,可以使用volatile变量禁止指令重排序。针对volatile修饰的变量,在读写操作指令前后会插入内存屏障,指令重排序时不能把后面的指令重排序到内存屏。 ```java double r = 2.1; //(1) double pi = 3.14;//(2) double area = pi*r*r;//(3) ``` 虽然代码语句的定义顺序为1->2->3,但是计算顺序1->2->3与2->1->3对结果并无影响,所以编译时和运行时可以根据需要对1、2语句进行重排序。 ## 指令重排带来的问题 如果一个操作不是原子的,就会给JVM留下重排的机会。 ```java 线程A中 { context = loadContext(); inited = true; } 线程B中 { if (inited) fun(context); } ``` 如果线程A中的指令发生了重排序,那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。 ## 禁止指令重排的原理 volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。 JVM内存屏障插入策略: * 在每个volatile写操作的前面插入一个StoreStore屏障; * 在每个volatile写操作的后面插入一个StoreLoad屏障; * 在每个volatile读操作的前面插入一个LoadLoad屏障; * 在每个volatile读操作的后面插入一个LoadStore屏障。 ## 原子操作 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" ,指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。 # 总结 1、volatile是**轻量级同步机制**。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比synchronized关键字更轻量级的同步机制。 2、volatile**无法同时保证内存可见性和原子性**。加锁机制既可以确保可见性又可以确保原子性,而volatile变量**只能确保可见性**。 3、volatile不能修饰写入操作依赖当前值的变量。声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。 4、当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile; 5、volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。