简介
Java虚拟机规范中试图定义了一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
主内存与工作内存
- 主内存-Main Memory
Java内存模型规定来所有变量都存储在主内存中 - 工作内存-Working Memory
每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量
不同线程之间也无法直接访问其他工作内存中的变量,线程间变量值的传递均需要通过主内存来完成 - 对应的JVM内存区域
主内存对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域
内存间交互操作
JMM中定义了8种操作方式,虚拟机实现时必须保证每一种操作都是原子的、不可再分的
- lock-锁定
把主内存的变量标识为一条线程独占的状态 - unlock-解锁
把一个处于锁定状态的主内存的变量释放,释放后的变量才可以被其他线程锁定 - read-读取
把主内存变量的值传输到线程的工作内存中,以便随后的load操作使用 - load-载入
把read操作中从主内存中得到的变量值放入工作内存的变量副本中 - use-使用
把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时就会执行这个操作 - assign-赋值
把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 - store-存储
把工作内存中的变量的值传送到主内存中,以便随后的wirte操作使用 - write-写入
把store操作从工作内存中得到的变量的值放到主内存的变量中
原子性、可见性、有序性
JMM是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的
原子性-Atomicity
JMM提供了lock和unlock操作来满足这种需求,lock和unlock用户不能直接使用但是提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,反映到Java代码就是synchronized
可见性-Visibility
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改,共有三种方式实现:
- volatile
volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新 - synchronized
会对变量进行lock操作,对于一个变量执行unlock操作之前,必须先把变量同步回主内存中(执行store、write) - final
final 常量无需同步,就能被其它线程正确访问
有序性-Ordering
如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的
前半句指线程内表现为串行的语意,后半句是指“指令重排序”和“工作内存于主内存同步延迟”
volatile和synchronized两个关键字来保证线程之间操作的有序性
- volatile
volatile关键字本身就包含了禁止指令重排序的语义 - synchronized
synchronized是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的,从而保证两个同步块只能串行地进入
happens-before原则
happens-before(先行发生)原则,先行发生原则是JMM中定义的两项操作的偏序关系,具体如下
- 程序顺序规则-
Program Order Rule
如果程序中操作A在操作B之前,那么线程中A操作将在B操作之前执行 - 监视器锁规则-
Monitor Lock Rule
unlock(退出同步方法/块)发生在每次后续获取同一个监视器锁lock之前
例如:操作A进行lock,操作B想要进行lock之前,操作A必须进行unlock - volatile变量规则-
Volatile Variabel Rule
volatile变量的,在每次读取操作之前,先执行写入操作 - 线程启动规则-
Thread Start Rule
Thread的start()方法为该线程的run()方法之前进行的操作 - 线程结束规则-
Thread Termination Rule
线程的所有操作都发生在线程终止之前 - 线程中断规则-
Thread Interruption Rule
当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行 - 对象终结规则-
Finalizer Rule
对象的构造函数必须在启动该对象的Finalizer操作之前完成 - 传递性-
Transitivity
如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行
线程状态转换
Java中定义了5中线程状态,在任意一个时间点,一个线程只能有且只有其中的一种状态
- 新建-
New
创建后尚未启动的线程处于这种状态 - 运行-
Runable
Runbale包含了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,也有可能正在等待CPU为它分配执行时间 - 无限等待-Waiting
处于这种状态的线程不会被分配CPU执行时间,它们需要等待被其他线程显示的唤醒,一下方式会让线程陷入无期限的等待状态- 无timeout参数的Object.wait()
- 无timeout参数的Thread.join()
- LockSupport.park()
- 限期等待-Timed Waiting
处于这种状态的线程也不会被分配CPU执行时间,不过无需等待被其他线程显式地唤醒,在一定时间之后会由系统自动唤醒,一下方法会让线程进入限期等待状态- Thread.sleep()
- 设置timeout参数的Object.wait()
- 设置timeout参数的Thread.join()
- LockSupport.parkNanos()
- LockSupport.parkUntil()
- 阻塞-Blocked
等待获取到一个排他锁,在程序等待进入同步区域的时候,线程进入这种状态 - 结束-Terminated
已终止线程的线程状态,线程已经结束执行
转换关系如图所示:













网友评论