JVM——Java内存模型(JMM)

JVM——Java内存模型(JMM)

  到底什么是Java内存模型?Java内存模型定义的主要目标是什么?

前言

  Java内存模型:屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

  Java内存模型的主要目标:定义程序中各个变量的访问规则,即JVM如何将这些变量从内存中取出来以及如何将变量再写回内存等细节。注意:此处的变量指实例字段,静态字段和构造数组对象的元素(线程共享)不包括局部变量和方法形参(线程私有)。

  JVM如何实现Java内存模型(Java Memory Model)?

一、主内存与工作内存的关系

  1、主内存是所有线程共享的区域,而工作内存是每个线程独有的区域。

  2、JMM规定所有变量都必须存储在主内存中。

  3、线程中的工作内存中保存了该线程使用到的变量的主内存副本

  4、线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行(操作那个副本),不能直接读写主内存中的变量

  5、不同线程之间无法直接访问彼此的工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

  用一张图很形象的描述关系:

JVM——Java内存模型(JMM)

  主内存与工作内存存在延迟/多线程不加锁会出现错误的原因?

  若一次开启多个线程,就会出现多个工作内存,由于工作内存中的变量是主内存的副本,若每个线程都对其工作线程中的变量进行修改或读取操作,由于主内存与工作内存存在延迟,则会出现脏读等一系列错误。若给该变量加上锁后,每次只能有一个线程能够造作该变量,即使主内存与工作内存存在延迟,也不会有多线程错误(其他线程等着该线程执行完才能开始执行)

二、内存间交互操作

  主内存或工作内存之间到底是如何交互的?
JVM——Java内存模型(JMM)

 总结一下:

  若想将一个变量从主内存复制到工作内存中,需要依次执行read和load操作。

  若想将一个变量从工作内存同步会主内存中,需要依次执行store和write操作

  Java内存模型的三大特性是什么?

三、Java内存模型的三大特性

  要想并发程序正确的运行,必须同时保证原子性、可见性以及有序性,只要有任意一个没有被保证,就有可能导致程序运行不正确

  1、原子性:一个或多个操作,要么全部执行并且执行过程不会被任何因素打断,要么就不执行

   基本数据类型的访问读写(赋值,读取)是具备原子性的,而运算(++,–)不是。而若需要更大范围的原子性,就需要synchronized锁或Lock锁的支持。利用多线程加锁技术可以保证非原子性的操作保持原子性。

  2、可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

   volatile,synchronized、final可以实现可见性

  3、有序性:若在本线程内观察,所有的操作都是有序的(线程内表现为串行),若在线程中观察另一个线程,所有的操作都是无序的(指令重排序+工作内存与主内存存在延迟)

  Java内存模型的一些先天的"有序性",不需要通过任何手段就能保证的有序性。happens-before(先行发生)原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序

JVM——Java内存模型(JMM)

  什么是volatile?

四、volatile变量的特殊规则

  若一个变量定义为volatile之后,它将具备两种特性:

  1、保证此变量对所有线程的可见性。

   对一个变量的写操作先行发生于后面对这个变量的读操作,意思就是当一个线程在对volatile变量进行写操作时,其他读线程必须等待该写线程写完后才能读。

   volatile只保证了可见性,但volatile变量的运算在并发情况下一样是不安全的(没有保证原子性和有序性,需要结合锁机制来保证线程安全)

  eg:若当前线程已经通过可见性获取了volatile变量的值,此时其他线程对该值进行修改并写回至主内存,而当前线程再次执行时,由于程序计数器的存在,会继续从获取值的下一步进行执行,如果当前线程再将变量修改,最终写回到主内存中的变量就是错误的。

  什么时候只是用volatile就能保证线程安全?

  ①运算结果不依赖当前变量的值,或能够保证只有单一的线程修改变量的值。

  ②变量不需要与其他的状态变量共同参与不变约束。

  若满足以上两条,只是用volatile就能保证线程安全,否则还是要借助锁机制。

  volatile禁止指令重排?

  2、禁止指令重排

  普通变量仅仅会保证该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序和程序代码中的顺序一致。(普通变量不能保证先赋值还是先执行程序的代码),这就导致了,如果先执行程序代码,而代码中使用了普通变量,该变量还没被赋值,导致错误。

  volatile变量就禁止指令重排,一定是先进行volatile变量的赋值,再执行程序代码,保证了程序代码执行时,变量已经是被赋过值的。(这就是为什么懒汉式单例需要将变量设置为volatile了)