JUC并发编程第六篇,带你了解Java内存模型JMM
- 一、Java Memory Model(Java内存模型)是什么?
- 二、JMM规范三大特性
- 1. 可见性
- 2. 原子性
- 3. 有序性
- 三、JMM规范下多线程对变量的读写过程
- 四、JMM规范下多线程先行发生原则(happens-before)
一、Java Memory Model(Java内存模型)是什么?
- Java内存模型,简称JMM,是一种抽象的概念,用于描述一组规范约定,这个规范定义了程序中各个变量的读写访问方式,以及一个线程对共享变量的写入和对另一个线程的可见。
他能干嘛?
- 1.通过JMM来实现线程和主内存之间的抽象关系。
- 2.屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
技术要点
- JMM主要关注多线程的 原子性、可见性 和 有序性。
二、JMM规范三大特性
1. 可见性
- 指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更
- JMM规定了所有的变量都存储在主内存中。
- Java中普通的共享变量不保证可见性,因为数据修改被写入内存的时机是不确定的,多线程并发下很可能出现"脏读",所以每个线程都有自己的工作内存,线程在自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。
- 不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
2. 原子性
- 指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。
3. 有序性
- 对于一个线程的执行并不总是从上到下有序执行的。
- 为了提供性能,编译器和处理器通常会对指令序列进行重新排序。
- 指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致。
- 处理器在进行重排序时必须要考虑指令之间的数据依赖性
单线程环境可确保程序最终执行结果和代码顺序执行的结果一致。
多线程环境中线程交替执行,结果无法确定。
三、JMM规范下多线程对变量的读写过程
- 我们定义的所有共享变量都储存在物理主内存中;
- 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝);
- 线程对共享变量操作时,必须先在线程自己的工作内存中进行,然后写回主内存,不能直接在主内存中读写;
- 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行。
四、JMM规范下多线程先行发生原则(happens-before)
- 在JMM中,如果一个操作执行的结果需要对另一个操作可见 或者 代码重排序,那么这两个操作之间必须存在happens-before关系。
- Happens-Before 原则是判断数据是否存在竞争,线程是否安全的非常有用的手段。
Happens-Before规则
-
- 次序规则:
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
-
- 锁定规则:
一个unLock操作先行发生于后面对同一个锁的lock操作。(对于同一把锁,A线程需要先释放锁,B线程才能获得锁)
-
- volatile变量规则:
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的。
-
- 传递规则:
操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。
-
- 线程启动规则:
Thread对象的start()方法先行发生于此线程的每一个动作。
-
- 线程中断规则:
对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生(我先中断了你才能检测到)
-
- 线程终止规则:
线程中的所有操作都先行发生于对此线程的终止检测。(终止检测肯定在最后)
-
- 对象终结规则:
一个对象的初始化完成先行发生于它的 finalize() 方法的开始。(还没初始化完成,怎么可能就回收)
- 总之:如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。