并发系列之JVM内存模型、线程安全可见性
为什么会有内存模型?
案例
1 | package com.wyj.jvm.thread; |
执行结果
1 | 代码开始了 |
一直卡在while循环里面,没有i值打印出来
为什么不停止???
如何让他正确停止
1:volatile
1)根据内存模型的规定,保持可见性
2)ACC_VOLATILE访问控制,可以保证没有缓存立马可见
2:启动参数-client
-client(或 -server -Djava.compiler=NONE ——— 关闭优化)和-server重大区别在于jit优化关闭和开启
原因接着往下看
JVM运行时数据区
再看线程与线程之间读写的一张图
备注:(内存结构)运行时数据区———JVM java语言——— 工作内存和主内存
工作内存不仅仅是JVM,还包括cpu高速缓存
所以CPU等其他缓存,导致可见性(短时间内) , 但这一定不是主要原因,cpu缓存不会造成长时间不可见。
接着看其他可能原因
指令重排序
——— 在不改变程序运行结果的前提下,调整代码运行顺序
JVM运行模式 :编译、解释、混合
编译:字节码 — jit提前编译 – 汇编
解释:字节码 – 一段段编译 – 汇编
混合:-运行的过程中,JIT编译器生效,针对热点代码进行优化
看下官方文档给的优化案例(orcale文档)
优化是针对单CPU
看下demo里的thread1
1 | Thread thread1 = new Thread(new Runnable() { |
对于这个线程来说,只有读,JIT优化器——— 循环读——— 读一次,所以我们上面while里面的代码在被优化后,变成了
1 | while (demo1.flag) { |
反思:这样明明会导致出错,为什么还要有JIT优化?
线程就出现了所见非所得,运行结果不可预测。所以就有了内存模型的概念,只要程序的所有执行产生的结果都可以由内存模型预测,内存模型决定了再程序的每个点上可以读取什么值。
JVM运行时数据区设计,多个内存区间进行交互势必会有问题,Java语言 提出 内存模型的相关规范。
共享变量描述:可以在线程之间共享的内存称为共享内存或堆内存。所有实例字段、静态字段和数组元素都存储在堆内存中
volatile能禁止重排序,JIT不能改变语法,从而达到可见性的目的。
synchronized(加锁)后,为什么也能保证数据一致性?因为加锁就意味着在synchronized里面的代码块执行的时候是单线程,没有数据竞争,那么程序的所有执行看起来都是顺序一致的。
Happens-before 先行发生原则(先发生先生效):主要用于强调两个有冲突的动作之间的顺序,以及定义数据争用的发生时机。比如加了volatile关键字的变量就满足这个原则。
Volatile关键字
可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
根据JMM中规定的happen before 和同步原则:
对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作
对volatile变量v的写入,与所有其他线程后续对v的读操作
要满足这些条件,所有volatile关键字就有这些功能:
1:禁止缓存
volatile变量的访问控制符会加个c(反编译class文件能看出来)
从官方文档看下ACC_VOLATILE的含义
ACC_VOLATILE可保证立马可见,原因就是没有缓存。
2:对volatile变量相关的指令不做重排序