1. 什么是Java内存模型
- Java内存模型(JMM)设计的目的是为了保证Java程序在各种平台下都能无视硬件和操作系统差异,保证程序访问内存的一致性。
- JMM规定一切变量存储于主内存当中,每个工作线程都有一个属于自己的工作内存。工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的操作只能发生在工作内存当中,而不能直接读写主内存中的变量。不同线程无法直接访问其他线程的工作内存,线程之间变量值的传递需通过主存过渡。
2. 主内存与工作内存的交互
2.1 交互操作
JMM定义了以下8中操作进行主内存与工作内存的交互,每种操作都是原子的不可再分的
- lock:作用于主内存,把一个变量标识为线程独占状态。
- unlock:作用于主内存,将一个变量解锁。
- read:作用于主内存,将一个变量值读入工作内存,以便进行load操作。
- load:作用于工作内存,将读入的变量值放入工作内存的变量副本之中。
- store:作用于工作内存,将变量的值传入主内存,以便进行write操作。
- write:作用于主内存,将传入主内存的的值放入主内存变量值中。
- use:作用域工作内存,将工作内存变量的值送至执行引擎进行相应的操作。
- assign:作用域工作内存,将从执行引擎获取到的值赋给工作内存中的变量。
2.2 操作的约束
- read和load、store和write需按序执行,且成对出现。
- 变量在工作内存发生变化后需写回主内存。若未发生变化则不写回。
- 不允许工作内存中直接使用 一个未被load或assign的变量。
- 如果对一个变量执行lock操作,那将会清空之前工作内存中此变量的值,并重新从主内存中读入该变量的值。
- 执行unlock之前,需要把此变量同步回主内存。
3. violatile关键字
3.1 两个特性
当一个变量被violatile修饰后,该变量具有两个特性:
- 对所有线程的可见性,当一个线程修改变量的值后,新值对于其他线程来说能够立即得知。
在不符合以下两条规则的场景中,我们仍需要加锁或者使用原子类来保证原子性。- 运算结果并不依赖当前值,或者能够保证只有单一的线程修改变量值。
- 变量不需要与其他的状态变量共同参与不变约束。
- 禁止指令重排序优化。
3.2 该关键字对于操作的约束
假设T表示一个线程,V和W代表被voilatile修饰的两个关键字,那么在进行read、load、use、assign、store和write操作时需满足以下规则:
- 只有当T对V执行的前一个动作为load时,T才能对V进行use。只有T对V的后一个动作为use时,T才能对V进行load。
- 只有T对V执行的前一个动作是assign时,T才能对V进行store。只有T对V的后一个动作是store时,T才能对V进行assign。
- 假定A为T对V实行的use或assign,P是与之相应的read或write。B是T对W进行的use或assign,Q是与之相应的read或write。如果A先于B,则P先于Q。
4. 先行发生原则
-
他是判断数据是否存在竞争、线程是否安全的主要依据。该原则具体指,如果A先行发生于B,也就是说,在发生B操作之前,操作A产生的影响能被B观察到,“影响”包括修改了共享变量的值、发送了消息等。
-
下面是JMM中天然的先行并发关系,这些关系无需任何同步代码的协助就已经存在,如果两个操作之间的关系不在此列,且无法从下列规则推导出的话,他们就没有顺序性保障,虚拟机可以对他们进行任意排序。
- 程序次序规则:在一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
- 管程锁定规则:一个unlock操作先行发生于后面(时间上的先后)对同一个锁的lock操作。
- violatile变量规则:对一个violatile的写操作先行发生于后面(时间上的先后)对这个变量的读操作。
- 线程启动规则:Thread对象的
start()
方法先行发生于此线程中的每一个动作。 - 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。
- 线程中断规则:对线程
interrupt()
方法的调用先行发生于被中断线程的代码检测到被中断事件的发生。 - 对象终结原则:一个对象初始化的完成先行发生于他的
finalize()
方法的开始。 - 传递性:如果A先行发生于B,B先行发生于C,则A先行发生于C。
-
一个操作时间上的先发生与先行发生毫无关系。