文章目录
- 前置的硬件知识
- 什么是JMM
- JMM的三大特性
- JMM中定义的原子操作
- happens-before先行发生原则
前置的硬件知识
硬件存储体系:
运行速度从上到下依次减慢.
由于CPU
的计算速度远超与内存的处理速度,所以CPU
不会直接从内存中读写,而是将内存中的变量拷贝一份副本放到CPU
高速缓存中,缓解主存储器和CPU
速度不匹配的瓶颈.但这样就导致了内存的读和写操作的时候会出现不一致的问题,即缓存一致性问题.
什么是JMM
JMM
(Java
内存模型,即Java Memory Model
),本身并不是一个真实存在的模型,只是一种抽象的概念,仅仅描述的是一组约定或者规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问形式,并决定了一个线程对共享变量何时写入以及何时对其他线程可见,关键技术点都是围绕多线程的原子性,可见性和有序性展开的.
JMM
实现了线程和主内存之间的抽象关系.JMM
也屏蔽了各个硬件平台和操作系统的内存访问差异,以实现Java
程序在各种平台下都能达到一致性访问的效果.
由于JVM
运行程序的实体是线程,而 每个线程创建时JVM
都会为其创建一个工作内存(有些地方成为栈空间) ,工作内存是每个线程 私有的数据区域,而Java
内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有的线程都可以访问,但线程对变量的操作(读或者写)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完成之后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程之间无法访问对方的工作内存,线程之间的通信(传值)必须通过主内存来完成.
这里所说的主内存和工作内存与java
内存的java
堆、栈、方法区等并不是同一层次的内存划分。如果要比较的话,那么主内存主要对应于java
堆中对象实例数据部分,而工作内存则对应虚拟机栈中的那部分区域。从更低的层次来说,主内存就是硬件的内存,而为了获取更好的有运行速度,虚拟机及硬件系统,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。
JMM的三大特性
-
原子性
指一个操作是不可分割的、完整的,要么全部执行成功,要么全部不执行,不存在执行一半的情况。在多线程环境下,如果一个操作不是原子性的,那么可能会发生竞态条件(race condition
)等问题,导致程序出现不可预期的错误。为了保证原子性,可以使用synchronized
关键字或者使用Atomic
类中提供的原子操作。 -
有序性
什么是指令重排序
对于一个线程的执行代码而言,我们总是习惯性地认为代码的执行是从上到下,有序执行.但是为了提高性能,编译器和处理器通常会对指令序列进行重新排序.Java
固定JVM
线程内部维持顺序化语义,即只要程序的最终结果与他顺序化执行的结果相等,那么指令的执行顺序可以与代码的顺序不一致,此过程叫做指令重排序.
- 优点:
JVM
能根据处理器特性(CPU
多级缓存系统,多核处理器)适当的对机器指令进行重排序,使机器指令能更符合CPU
的执行特性,最大限度的发挥机器性能. - 缺点
指令重排序只能保证串行语义一致,但没义务保证多线程之间的语义也一致.所以在多线程的情况下会产生"脏读"
- 可见性
- 可见性是指一个线程对共享变量的修改,其他线程能够及时看到这些修改。
JMM
通过内存屏障、volatile
关键字和同步机制(如synchronized
和Lock
)来确保可见性。 - 当一个线程修改了一个变量并释放了锁,其他线程在获取该锁后能够看到这个变量的最新值。
JMM中定义的原子操作
JMM
内存模型中定义的8
种每个线程自己的工作内存与主物理内存之间的原子操作
read
(读取)->load
(加载)->use
(使用)->assign
(赋值)->store
(存储)->write
(写入)->lock
(锁 定)->unlock
(解锁)
read
:作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存load
:作用于工作内存,将read
从主内存传输的变量值放入工作内存变量副本中,即数据加载use
:作用于工作内存,将工作内存的变量副本的值传递给执行引擎,每当JVM
遇到需要该变量的字节码指令时会执行该操作assign
:作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM
遇到一个给变量赋值字节码指令时会执行该操作store
:作用于工作内存,将赋值完毕的工作变量的值写回主内存write
:作用于主内存,将store
传输过来的变量值赋值给主内存中的变量lock
:作用于主内存,将一个变量标记为一个线程独占的状态,只是写时加锁,就只是锁了向主内存写入变量的过程;unlock
:作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
happens-before先行发生原则
Happens-Before
(先行发生)原则是对Java
内存模型(JMM
)中所规定的可见性的更高级的语言层面的描述。用这个原则解决并发环境下两个操作之间的可见性问题,而不需要陷入Java
内存模型苦涩难懂的定义中。
注意:
Happens-Before
原则其实就是指第一个操作A
的执行结果对第二个操作B
的执行结果可见,而且第一个操作的执行顺序排在第二个操作之前.
但是两个操作之间存在happens-before
关系,并不意味着一定要按照happens-before
原则指定的顺序执行,如果重排序之后的执行结果按照happens-before
关系来执行的结果一致,那么这种重排序并不违法.
- 次序规则:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
- 管道锁定规则:一个
unlock
操作先行发生于后面对同一个锁的lock
操作。 volatile
变量规则:对一个volatile
变量的写操作先行发生于后面对这个变量的读操作。- 传递规则:如果操作
A
先行发生于操作B
,操作B
先行发生于操作C
,那就可以得出操作A
先行发生于操作C
的结论。 - 线程启动规则:
Thread
对象start()
方法先行发生于此线程的每一个动作。 - 线程中断规则:对线程
interrupt()
方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()
方法检测到是否有中断发生。 - 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过
Thread.join()
方法和Thread.isAlive()
的返回值等手段检测线程是否已经终止执行。 - 对象终结规则:一个对象的初始化完成(构造函数结束)先行发生于它的
finalize()
方法的开始。