Java Memory Model --volatile keyword

  Recent work has used the volatile keyword, has always been simple to use, did not read carefully the relevant content, take this opportunity to put together detailed information under the relevant volatile placed on the record for future reference.

  First we look at a small example:

 1 public class VolatileDemo1 {
 2     private boolean flag = true;
 3 
 4     public static void main(String[] args) throws InterruptedException {
 5         VolatileDemo1 demo = new VolatileDemo1();
 6         Thread thread = new Thread(() -> {
 7             long start = System.currentTimeMillis();
 8             while (demo.flag) {
 9             }
10             long end =System.currentTimeMillis ();
 . 11              System.out.println ( "termination of the flag value of the while loop:!" + Demo.flag);
 12 is              System.out.println ( "Processed:" + (End - Start)) ;
 13 is          });
 14          Thread.start ();
 15          TimeUnit.SECONDS.sleep (2 );
 16          demo.flag = to false ;
 . 17      }
 18 is }

  This code is one of the typical application scenarios volatile keyword, two threads (the main thread and the thread thread) for information exchange through a shared variable, on a section of code, coupled with the absence of the volatile keyword to flag variables, foresee, while circulation of the thread thread does not jump out. Well, you are not only adding the volatile keyword can solve this problem? Or that we can not fail to change the code to achieve the purpose of (the main thread after changing the value of the flag, thread thread can read, you can jump out of the while loop). The answer of course is yes, we can use the following ways :( Figure)

  在虚拟机参数选项上加上-Xint(请注意,这个参数在JDK1.8版本及以上),同样能够使while循环跳出,那么这个-Xint参数到底有什么作用呢?请看以下截图:

  这张截图来自Oracle的Java HotSpot VM Options 官方文档,翻译过来的意思是:“以纯解释模式运行应用程序。禁用编译到本机代码,所有字节码由解释器执行。在这种模式下,just in time (JIT)编译器所提供的性能优势并不存在。”这么说只要是禁止了JIT即时编译,就起到了和加volatile一样的作用,那他们两个有什么区别吗?JIT即时编译又做了什么呢?请继续往下看。

  要扯明白上面的问题,我们还要说下Java的内存模型,首先什么是内存模型呢?周所周知,在现代计算机硬件系统的不断改进中,CPU和内存之间的多级缓存机制导致的缓存一致性问题,以及为了高效执行代码而进行的处理器优化和指令重排序问题,是并发编程中的可见性、原子性、有序性问题的硬件层面原因。在并发编程中,为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

  说的直白点,内存模型就是解决多线程场景下并发问题的一个重要规范,而不同的编程语言对于这个规范,在实现上可能有所不同,而Java内存模型(Java Memory Model ,JMM)就是Java编程语言提供的一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。同时Java中提供了一系列和并发处理相关的关键字,比如volatilesynchronized等,其实这些关键字就是对Java内存模型规范的一种实现,他们封装了Java内存模型规范底层的实现后提供给程序员使用,用来解决Java并发编程问题。

  有了上面的对于内存模型的描述,那么我们就很好理解了,如果要解决并发编程中的问题,最简单直接的做法就是不使用处理器执行代码的优化技术、不使用指令重排序、不使用CPU缓存等等优化技术。但是,这么做显然就是因噎废食了。而我们使用的-Xint参数,根据官网的的描述(没有JIT编译的部分,全部是由解释器解释执行)和最终的执行结果看,我们可以推论出:-Xint参数的作用在废止JIT即时编译应该也就是废除了指令重排序和CPU缓存等优化技术,这里我没有深入的研究过这个参数和JIT,只是临时应用,所以做出的推论完全是根据官网的的描述和代码的执行结果看,不一定完全正确,如果有了解的大神,还请不吝赐教。

  那下面我们就来看看volatile关键字,显然volatile关键字不会和-Xint一样因噎废食,全面封杀优化技术,那他是怎么做的呢?

  首先内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障,volatile作为内存模型规范的一种应用实现方式,自然也是实现这两种方式。

  对于volatile变量,生成的汇编代码在volatile修饰的共享变量进行写操作的时候会多出一个Lock前缀的指令,将这个缓存中的变量回写到系统主存中。

  lock前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

  功能1相当于禁止指令重排序优化,解决了并发变成中有序性问题。

  功能2和3,由于他处理器的缓存遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile变量在并发编程中,其值在多个缓存中是可见的,解决了并发变成中可见性问题。

  注意:volatile并不能解决原子性问题。

  通过以上一波操作,volatile完成了并发编程时解决有序性和可见性问题。

 

 补充内容:

 Java虚拟机有3种执行方式,分别是解释执行、混合模式和编译执行,默认情况下处于混合模式中

编译:字节码 --- jit提前编译 -- 汇编

解释:字节码 – 一段段编译 – 汇编

混合 :– 运行的过程中,JIT编译器生效,针对热点代码进行优化

 

内存屏障参考资料:

https://blog.csdn.net/dd864140130/article/details/56494925

参考资料:

http://www.uucode.net/201504/jvm5

https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

Guess you like

Origin www.cnblogs.com/peripateticism/p/11065864.html