可见性
- 使用volatile修饰变量就不会从寄存器中获取该变量的值,而是从内存(高速缓存)中获取
- 使用volatile关键字会强制将修改的值立即写入主存,导致别的线程的工作内存中缓存变量无效
-
原子性
volatile 只保证单次读/写操作的原子性,对于多步操作,volatile 不能保证原子性。i++,在多线程环境下,最终的结果是不确定的。因为++操作,被编译为指令后,是多个指令来完成的。那么遇到并发的情况,就会导致彼此“覆盖”的情况。
有序性(禁止指令重排序优化)
处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
语句1和语句2没有数据依赖性,因此可能会被重排序。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。happen-before重要规则
- 顺序执行规则(限定在单个线程上的):该线程的每个动作都happen-before它的后面的动作。
- 隐式锁(monitor)规则:unlock happen-before lock,之前的线程对于同步代码块的所有执行结果对于后续获取锁的线程来说都是可见的。
- volatile读写规则:对于一个volatile变量的写操作一定会happen-before后续对该变量的读操作。
- 多线程的启动规则:Thread对象的start方法happen-before该线程run方法中的任何一个动作,包括在其中启动的任何子线程。
- 多线程的终止规则:一个线程启动了一个子线程,并且调用了子线程的join方法等待其结束,那么当子线程结束后,父线程的接下来的所有操作
都可以看到子线程run方法中的执行结果 - 线程的中断规则:可以调用interrupt方法来中断线程,这个调用happen-before对该线程中断的检查(isInterrupted)
如何保证volatile修饰变量的原子性?
加锁
- 使用原子类(AtomicBoolean)
- 赋值变量中就不能出现被多线程所共享的变量,哪怕这个变量也是个volatile也不可以。
- 实现long/double类型变量的原子操作
volatile常用场景
单例模式,cas局部变量 ``` volatile与锁类似的地方有两点: - 确保变量的内存可见性
- 防止指令重排序 volatile可以确保对变量写操作的原子性,但不具备排他性 另外的重要一点在于:使用锁可能会导致线程的上下文切换(内核态与用户态之间的切换),但使用volatile并不会出现这种情况 ```
volatile原理
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”。
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
内存屏障
是一个CPU指令。a)确保一些特定操作执行的顺序; b)影响一些数据的可见性(可能是某些指令执行后的结果)。
编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。
强制更新一次不同CPU的缓存。
防止指令重排序与实现变量的可见性都是通过一种手段来实现的:内存屏障(memory barrier)
int a = 1;
String s = “hello”;
内存屏障 (Release Barrier,释放屏障)
volatile boolean v = false; // 写入操作
内存屏障 (Store Barrier,存储屏障)
Release Barrier:防止下面的volatile与上面的所有操作的指令重排序。
Store Barrier:重要作用是刷新处理器缓存,结果是可以确保该存储屏障之前一切的操作所生成的结果对于其他处理器来说都可见。
内存屏障(Load Barrier,加载屏障)
boolean v1 = v;
内存屏障(Acquire Barrier,获取屏障)
int a = 1;
String s = “hello”;
Load Barrier:可以刷新处理器缓存,同步其他处理器对该volatile变量的修改结果。
Acquire Barrier:可以防止上面的volatile读取操作与下面的所有操作语句的指令重排序。
对于volatile关键字变量的读写操作,本质上都是通过内存屏障来执行的。
内存屏障兼具了两方面能力:1. 防止指令重排序, 2. 实现变量内存的可见性。
1. 对于读取操作来说,volatile可以确保该操作与其后续的所有读写操作都不会进行指令重排序。
2. 对于修改操作来说,volatile可以确保该操作与其上面的所有读写操作都不会进行指令重排序。
ArrayList
volatile与锁的一些比较
锁同样具备变量内存可见性与防止指令重排序的功能。
monitorenter
内存屏障(Acquire Barrier,获取屏障)
……
内存屏障 (Release Barrier,释放屏障)
monitorexit
Java内存模型(Java Memory Model, JMM)以及happen-before
1. 变量的原子性问题。
2. 变量的可见性问题
3. 变量修改的时序性问题。