从顺序一致性,到volatile
顺序一致性
嗯?程序不是按我写的顺序执行的吗
image.png
那只是理性中的模型而已。在多线程的环境下,顺序一致性模型也只能保证每个线程操作的执行顺序是一致的。假设有两个线程访问相同的代码,可能就有两种结果。
一种可能:
image.png
还有一种可能:
image.png
所以,我们要十分当心JMM模型,对于未同步的程序,和顺序一致性模型的区别。保证的那些,没有保证那些,被自己的一厢情愿的认知忽悠。
顺序一致模型:单线程操作内会按顺序执行😄
JMM:单线程操作存在指令重排序,不能保证单线程操作会按顺序执行🙄
顺序一致模型:所有线程只能看到一致性的执行顺序😄
JMM:不保证所有线程看到一致性的执行顺序🙄
顺序一致模型:所有对内存的读写操作具有原子性😄
JMM:对long,double类型JMM不保证操作的原子性🙄
为了了解volatile,先用起来,写一个小demo
public class TestVolitle {
public volatile long sum = 0;
public int add(int a, int b) {
int temp = a + b;
sum += temp;
return temp;
}
public static void main(String[] args) {
TestVolitle test = new TestVolitle();
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum = test.add(sum, 1);
}
System.out.println("Sum:" + sum);
System.out.println("Test.sum:" + test.sum);
}
}
为了了解volatile,到底干了什么。我们用javac编译成JVM指令看下,并没有看到有什么特殊的地方,也就是说,字节码层面volatile变量并没有什么不同。
image.png
那么,我们就需要看汇编指令级别的区别。Mac系统需要下载hsdis-amd64.dylib,使用JitWatch工具,才能看到汇编指令,有点麻烦。(需要下载源码编译,=。=,自己写的class过于简单,被认为不需要JIT编译,有小小的波折,但是对学JVM还是很有帮助的)
image.png
我们也可以通过IDEA,设置VM参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly,打印出汇编指令。
image.png
看到汇编指令,JVM编译成汇编指令的时候加了lock前缀。
image.png
lock前缀的意义
- Lock前缀指令会让CPU缓存会写到内存
- 使CPU缓存无效,直接从主存拿值
Volatile的特性
- 可见性:对volatile的读总是能看到对此变量最后的写操作
举个例子,上代码
public class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // step 1
flag = true; // step 2
}
public void reader() {
if (flag) { // step 3
System.out.println(a); // step 4
}
} }
套用学过的理论,一个线程对volatile变量的write操作,会将本地CPU缓存中共享变量的值写到内存里;一个线程对volatile变量的read操作,会将本地CPU缓存中共享变量的值失效,去主存中拿值。
volatile读操作和锁释放有相同的内存语义
volatile写操作和锁获取有相同的内存语义
volatile看做一把“锁”,对于一个volatile变量,不同线程的读写通过volatile来同步。
也复合happens-before原则:
对一个锁的解锁操作,happens-before后续对这个锁的加锁操作。
意味着对volatile的读总是可以看到此变量最后的写入操作。
2.禁止指令重排序:禁止volatile变量和普通变量的重排序
JMM是通过什么来禁止指令重排序的呢。答案就是JMM介绍的内存屏障。
JMM在volatile读写操作前后加了内存屏障,为了优化性能,实际的重拍规则是这样的:
1.volatile读之后,后面的第二的操作都不能重排序
- volatile写之前,前一个操作都能重排序
3.volatile写,后面一个是volatile读,不能重排序 - 普通变量读,后面是volatile读,可以重排序
3.原子性: 指单一对volatile变量操作,不被其他调到打断,动作不可分
所谓的原子性,就是在执行过程中不会被线程调度机制打断的操作,这种操作从开始就一直运行到结束,中间不存在任何上下文切换。
对于复合操作volatile++不符合原子性。
volatile 怎么用
BB了这么多,最关心的还是volatile应该怎么用。
首先是个轻量级锁,只能保证单个volatile变量有原子性,经典的案例:双重锁检查(不想具体写了,早资料自己推演一下😀)
扩展阅读 :正确应用的volatile几种应用场景。写的很好,下个该了synchronized,这个晚点更,要考可信了,更新刷题技巧算法吧。😝
https://www.ibm.com/developerworks/cn/java/j-jtp06197.html
PS:拱了三天的卒,写出来这一篇。感觉有点难。想要不停的输出,就要N倍的输入。情绪和持续状态会毁掉一个人。
image.png












网友评论