JVM中提供的四类内存屏障指令
- loadload:
读读,该屏障用来禁止处理器把上面的volatile读与下面的普通读重排序 - storestore:
写写,该屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中 - loadstore:
读写,该屏障用来禁止处理器把上面的volatile读与下面的普通写重排序 - storeload:
写读,该屏障的作用是避免volatile与后面可能有的volatile读/写操作重排序
volatile 特性
- 保证可见性
- 禁止指令重排
- 不保证原子性
volatile如何保证可见性
public class VolatileTest {
/**
* 使用volatile,1秒钟后程序会停止
* 不使用volatile,则不会停止
*/
static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
while (flag) {
}
System.out.println("over");
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
flag = false;
}, "t2").start();
}
}
复制代码
Java内存模型中定义的8种工作内存与主内存之间的原子操作
read读取
load加载
use使用
assign赋值
store存储
write写入
lock锁定
unlock解锁
- read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
- load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
- use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
- assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
- store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
- write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量 由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
- lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
- unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
volatile如何禁止指令重排
-
-
volatile有关禁止指令重排的行为
- 当第一个操作是 volatile 读时,不论第二个操作是什么,都不能重排序;这个操作保证了volatile读之后的操作不会被重排到volatile读之前
- 当第二个操作为 volatile 写时,不论第一个操作是什么,都不能重排序;这个操作保证了volatile写之前的操作不会被重排到volatile写之后
- 当第一个操作为 volatile 写时,第二个操作为 volatile 读时,不能重排序
-
-
-
四大内存屏障插入情况(具体见下面代码分析)
- 在每一个 volatile 写操作前面插入一个 storestore 屏障
- 在每一个 volatile 写操作后面插入一个 storeload 屏障
- 在每一个 volatile 读操作后面插入一个 loadload 屏障
- 在每一个 volatile 读操作后面插入一个 loadstore 屏障
-
volatile为什么不保证原子性?
volatile变量的复合操作(如i++)是不具有原子性的,原因是 i++ 操作从字节码角度来看,是分为三步的
多线程环境下,数据计算和数据赋值操作可能多次出现,即操作非原子。若数据再加载之后,若主内存中 count变量发生修改之后,由于线程工作内存中的值在此之前已经加载,从而不会对变更操作做出相应变化,即私有内存和公共内存中变量不同步,进而导致数据不一致
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的。
使用内存屏障分析下面代码
public class VolatileTest {
int i = 0;
volatile boolean flag = false;
public void write() {
i = 1;
flag = true;
}
public void read() {
if (flag) {
System.out.println("i=" + i);
}
}
}
复制代码
写操作:
- 在每一个volatile写操作 前面 插入一个 storestore屏障
- 在每一个volatile写操作 后面 插入一个 storeload屏障
| 操作 | 说明 |
|---|---|
| i = 1 | 普通写 |
| storestore屏障 | 禁止上面的普通写与下面的volatile写重排序 |
| flag = true | volatile写 |
| storeload屏障 | 禁止上面的volatile写与下面可能有的volatile读/写重排序 |
读操作:
- 在每一个volatile读操作 后面 插入一个 loadload屏障
- 在每一个volatile读操作 后面 插入一个 loadstore屏障
| 操作 | 说明 |
|---|---|
| if (flag) | volatile读 |
| loadload屏障 | 禁止上面的volatile读与下面的普通读重排序 |
| loadstore屏障 | 禁止上面的volatile读与下面的普通写重排序 |
| System.out. | 普通读 |
happens-before
-
顺序执行原则
- 一个线程中的操作 happends-before 该线程下后面的操作,也就是单线程下代码顺序不管怎么变,结果不变
-
volatile 规则
- 对于 volatile 修饰的变量的写操作 happends-before 之后的读操作
-
传递规则
- 1 happends-before 2,2 happends-before 3,所有 1 happends-before 3
-
start 规则
- start 启动之前的操作 对于 线程可见
-
join 规则
- join 启动之前的操作 对于 线程阻塞
-
监视器规则
- 锁对象的释放 happends-before 锁对象的加锁
happends-before :表示 前者的操作对后者可见