JVM——Java内存模型(JMM)
JVM——Java内存模型(JMM)
到底什么是Java内存模型?Java内存模型定义的主要目标是什么?
前言
Java内存模型:屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
Java内存模型的主要目标:定义程序中各个变量的访问规则,即JVM如何将这些变量从内存中取出来以及如何将变量再写回内存等细节。注意:此处的变量指实例字段,静态字段和构造数组对象的元素(线程共享)
,不包括
局部变量和方法形参(线程私有)。
JVM如何实现Java内存模型(Java Memory Model)?
一、主内存与工作内存的关系
1、主内存是所有线程共享
的区域,而工作内存是每个线程独有
的区域。
2、JMM规定所有变量
都必须存储在主内存
中。
3、线程中的工作内存中保存了该线程使用到的变量的主内存副本
。
4、线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行(操作那个副本),不能直接读写主内存中的变量
。
5、不同线程之间无法直接访问彼此的工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
用一张图很形象的描述关系:
主内存与工作内存存在延迟/多线程不加锁会出现错误的原因?
若一次开启多个线程,就会出现多个工作内存,由于工作内存中的变量是主内存的副本,若每个线程都对其工作线程中的变量进行修改或读取操作,由于主内存与工作内存存在延迟
,则会出现脏读等一系列错误。若给该变量加上锁后,每次只能有一个线程能够造作该变量,即使主内存与工作内存存在延迟,也不会有多线程错误(其他线程等着该线程执行完才能开始执行)
二、内存间交互操作
主内存或工作内存之间到底是如何交互的?
总结一下:
若想将一个变量从主内存复制到工作内存中,需要依次
执行read和load操作。
若想将一个变量从工作内存同步会主内存中,需要依次
执行store和write操作
Java内存模型的三大特性是什么?
三、Java内存模型的三大特性
要想并发程序正确的运行,必须同时保证原子性、可见性以及有序性,只要有任意一个没有被保证,就有可能导致程序运行不正确
1、原子性:一个或多个操作,要么全部执行并且执行过程不会被任何因素打断,要么就不执行
基本数据类型的访问读写
(赋值,读取)是具备原子性的,而运算(++,–)不是。而若需要更大范围的原子性,就需要synchronized锁或Lock锁的支持。利用多线程加锁技术可以保证非原子性的操作保持原子性。
2、可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
volatile,synchronized、final可以实现可见性
3、有序性:若在本线程内观察,所有的操作都是有序的(线程内表现为串行),若在线程中观察另一个线程,所有的操作都是无序的(指令重排序+工作内存与主内存存在延迟)
Java内存模型的一些先天的"有序性",不需要通过任何手段就能保证的有序性。happens-before(先行发生)原则。如果两个操作的执行次序无法从happens-before原则推导出来
,那么就不能保证它们的有序性
,虚拟机可以随意地
对它们进行重排序
。
什么是volatile?
四、volatile变量的特殊规则
若一个变量定义为volatile之后,它将具备两种特性:
1、保证此变量对所有线程的可见性。
对一个变量的写操作先行发生于后面对这个变量的读操作,意思就是当一个线程在对volatile变量进行写操作时,其他读线程必须等待该写线程写完后才能读。
volatile只保证了可见性,但volatile变量的运算在并发情况下一样是不安全的
(没有保证原子性和有序性,需要结合锁机制来保证线程安全)
eg:若当前线程已经通过可见性获取了volatile变量的值,此时其他线程对该值进行修改并写回至主内存,而当前线程再次执行时,由于程序计数器的存在,会继续从获取值的下一步进行执行,如果当前线程再将变量修改,最终写回到主内存中的变量就是错误的。
什么时候只是用volatile就能保证线程安全?
①运算结果不依赖当前变量的值,或能够保证只有单一的线程修改变量的值。
②变量不需要与其他的状态变量共同参与不变约束。
若满足以上两条,只是用volatile就能保证线程安全,否则还是要借助锁机制。
volatile禁止指令重排?