1. volatile的作用
a.volatile关键字可以简单保持赋值和返回操作的原子性,弱同步。
比如:读取和写入long和double不是原子性的操作,jvm会把64位(long和double)的读取和写入当作两个分离的32位操作来执行。这就产生了在一个读取和写入操作中间发生上下文切换,从而导致不同的任务可以看到不正确的结果 ,但是如果当你定义long和double变量时,使用volatile关键字,就会得到(赋值和返回操作)原子性。
b.volatile还可以确保应用中的可视性。
如果你将一个域声明为volatile,那么只要对这个域产生了写操作,那么所有的读操作就都可以看到这个修改。即便使用了本地缓存,情况也确实如果,volatile域会立即被写入主存,而读取操作就发生在主存中。
其实同步也会导致向主存中刷新,因此如果一个域完全有synchronnized方法或语句块来防护,那就不必将其设置为是volatile。
如果你将一个域定义为volatile,那么它会告诉编译器不要执行任何移除读取和写入操作的优化,这些操作的目的是用线程中的局部变量维护对这个域的精准同步。
注意:如果一个域的值依赖于它之前的值时,(例如递增一个计数器),volatile就无法操作了,示例:
package mutex.conflict;
import java.util.concurrent.*;
public class AtomicityTest implements Runnable {
private int i = 0;
public int getValue() { return i; }
private synchronized void evenIncrement() { i++; i++; }
public void run() {
while(true)
evenIncrement();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
AtomicityTest at = new AtomicityTest();
exec.execute(at);
while(true) {
int val = at.getValue();
if(val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
} /* Output: (Sample)
191583767
*///:~
输出:
191583767
该程序的主要目的是找到奇数值并终止。
尽管return i是原子性操作,但是缺少同步使其数值可以在处于不稳定的中间状态时被读取。除此之外,由于i也不是volatile的,因此还存在可视性问题。(不过这儿就是i是volatile也不起作用)所以getValue()和eventIncrement()必须是synchronized的。就可以解决问题。
学习总结自《java编程思想》
---------------------------------------------更新于2019年6月9号--------------------------------------
现在看了这篇文章,感觉总结的太浅显了。下面做一个深入的总结。
volatile的主要作用是:保证了变量修改的可见性,即一个线程对该变量的修改,对另外一个线程立马可见。
那么问题来了,为什么不用这个关键字修饰,就不可用呢?主要是两个原因
1.缓存导致内存不可见。
现在的cup的运行速度远远高于内存的读写速度, 为了让cpu不必因为等待读写内存数据而空闲。现在cpu和内存之前都会增加一道高速缓存cache, 而多核的cpu的每个核心都有自己的cache。当数据更新只在自己的cache, 还没有更新到主存中,别的线程这个时候是不可见的。
2. 指令重排序导致不可见
指令重排序是指: cpu采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。在应用程序就是,提高代码执行性能,指令的执行顺序被编译器或者运行时环境调整顺序的现象。重排序分为编译期重排序和运行期重排序,编译期重排序指的是编译期在编译源代码的时候对代码的执行顺序进行分析,在无论怎么重排序,不影响最终的执行结果的情况下,对代码进行重排序,以提高代码执行性能。运行期重排序,指的是为了提高指令流水并行执行能力,系统对机器指令执行顺序的调整。
重排序是怎么导致不可见的呢?
由于指令顺序的调整,线程B读取某个变量的时候,线程1可能还没有进行写入操作,虽然代码顺序是线程1写入代码在前面。
volatile的原理:
volatile不允许线程内部缓存,直接写入主存,而读取的时候,也是直接读取主存,不读取自己的cpu cache。
volatile 不允许指令重排。