^ _ ^
JMM
Java Memory Model,Java内存模型,本身是一种抽象概念,描述的是一组规则或规范,通过这组规范定义了程序中各个变量的访问方式
JMM关于同步的规定
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁和解锁是同一把锁
工作内存:线程的私有数据区域(栈空间)
主内存:进程中所有线程的共享区域
线程对主内存中的变量进行读写时,必须在工作内存中进行。首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写回主内存。
JMM需要保证的特性
- 可见性
- 原子性
- 有序性
volatile
Java虚拟机提供的轻量级同步机制
特点
- 保证可见性
- 不保证原子性
- 禁止指令重排
可见性实现原理
进程是资源分配的基本单位,进程中的变量都存储在主内存中。每个线程使用进程中的变量时,会将变量的值拷贝一份放入自己的工作内存中。但对于不加volatile修饰的变量,一个线程在自己的工作内存中对其进行修改后,不会立即写回主内存中,因此其他线程也就不知道这个变量发生的更改,造成了不可见性。
禁止指令重排实现原理
通过插入Memory Barrier指令(内存屏障)来禁止内存屏障前后的指令执行重排序优化。
- 在volatile写指令之前,插入
StoreStore
屏障,禁止上面可能的普通写和下面的volatile写重排序;在volatile写指令之后插入StoreLoad
屏障,禁止上面的volatile写和下面可能的读/写进行重排序 - 在volatile读后面插入
LoadLoad
屏障,禁止下面所有的普通读操作和上面的volatile读重排序;然后插入LoadStore
屏障,禁止下面所有的写操作和上面的volatile读重排序
volatile应用场景
1 | 1. memory = allocate() // 分配对象内存空间 |
由于2和3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中没有改变,因此这种重排序优化是允许的。
1 | 1. memory = allocate() // 分配对象内存空间 |
所以当一条线程访问instance不为null时,由于instance实例未初始化完成,也就造成了线程安全问题。