【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列( 九 )


Java内存模型的目标?定义程序中各个变量的访问规则 , 即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节 。此处的变量包括实例字段、静态字段和构成数组对象的元素 , 但是不包括局部变量和方法参数 , 因为这些是线程私有的 , 不会被共享 , 所以不存在竞争问题 。
主内存与工作内存所以的变量都存储在主内存 , 每条线程还有自己的工作内存 , 保存了被该线程使用到的变量的主内存副本拷贝 。线程对变量的所有操作(读取、赋值)都必须在工作内存中进行 , 不能直接读写主内存的变量 。不同的线程之间也无法直接访问对方工作内存的变量 , 线程间变量值的传递需要通过主内存 。

【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列

文章插图
内存间的交互操作一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存 , Java内存模型定义了8种操作:
【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列

文章插图
原子性、可见性、有序性
  • 原子性:对基本数据类型的访问和读写是具备原子性的 。对于更大范围的原子性保证 , 可以使用字节码指令monitorenter和monitorexit来隐式使用lock和unlock操作 。这两个字节码指令反映到Java代码中就是同步块——synchronized关键字 。因此synchronized块之间的操作也具有原子性 。
  • 可见性:当一个线程修改了共享变量的值 , 其他线程能够立即得知这个修改 。Java内存模型是通过在变量修改后将新值同步回主内存 , 在变量读取之前从主内存刷新变量值来实现可见性的 。volatile的特殊规则保证了新值能够立即同步到主内存 , 每次使用前立即从主内存刷新 。synchronized和final也能实现可见性 。final修饰的字段在构造器中一旦被初始化完成 , 并且构造器没有把this的引用传递出去 , 那么其他线程中就能看见final字段的值 。
  • 有序性:Java程序的有序性可以总结为一句话 , 如果在本线程内观察 , 所有的操作都是有序的(线程内表现为串行的语义);如果在一个线程中观察另一个线程 , 所有的操作都是无序的(指令重排序和工作内存与主内存同步延迟线性) 。
volatile什么是volatile?关键字volatile是Java虚拟机提供的最轻量级的同步机制 。当一个变量被定义成volatile之后 , 具备两种特性:
  1. 保证此变量对所有线程的可见性 。当一条线程修改了这个变量的值 , 新值对于其他线程是可以立即得知的 。而普通变量做不到这一点 。
  2. 禁止指令重排序优化 。普通变量仅仅能保证在该方法执行过程中 , 得到正确结果 , 但是不保证程序代码的执行顺序 。
为什么基于volatile变量的运算在并发下不一定是安全的?volatile变量在各个线程的工作内存 , 不存在一致性问题(各个线程的工作内存中volatile变量 , 每次使用前都要刷新到主内存) 。但是Java里面的运算并非原子操作 , 导致volatile变量的运算在并发下一样是不安全的 。
为什么使用volatile?在某些情况下 , volatile同步机制的性能要优于锁(synchronized关键字) , 但是由于虚拟机对锁实行的许多消除和优化 , 所以并不是很快 。
volatile变量读操作的性能消耗与普通变量几乎没有差别 , 但是写操作则可能慢一些 , 因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 。

经验总结扩展阅读