什么是Java内存模型中的happens-before
Java内存模型JMM
Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM中的主内存
- 存储Java实例对象
- 包括成员变量、类信息、常量、静态变量等
- 属于数据共享的区域,多线程并发操作时会引发线程安全问题
JMM中的工作内存
- 存储当前方法的所有本地变量信息,本地变量对其他线程不可见
- 字节码行号指示器、Native方法信息
- 属于线程私有数据区域,不存在线程安全问题
JMM与Java内存区域划分是不同的概念层次
- JMM描述的是一组规则,围绕原子性,有序性、可见性展开
- 相似点:存在共享区域和私有区域
主内存与工作内存的数据存储类型以及操作方式归纳
- 方法里的基本数据类型本地变量将直接存储在工作内存的栈帧结构中
- 引用类型的本地变量:引用存储在工作内存中,实例存储在主内存中
- 成员变量、static变量、类信息均会被存储在主内存中
- 主内存共享的方法是线程各拷贝一份数据到工作内存,操作完成后刷新回主内存
JMM如何解决可见性问题
指令重排序需要满足的条件
- 在单线程环境下不能改变程序运行的结果
- 存在数据依赖关系的不允许重排序
无法通过happens-befoore原则推导出来的,才能进行指令的重排序
A操作的结果需要对B操作可见,则A与B存在happens-before关系
i = 1;//线程A执行
j = i;//线程B执行
happens-before的八大原则
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
happens-before的概念
如果两个操作不满足上述任意一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这个两个操作进行重排序;
如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。
private int value = 0;
public void write(int input) {
value = input;
}
public int read(){
return value;
}
volatile:JVM提供的轻量级同步机制
- 保证被volatile修饰的共享变量对所有线程总是可见的
volatile的可见性
public class VolatileVisibility {
public static volatile int value = 0;
public synchronized static void increase(){
value++;
}
}
public class VolatileSafe {
volatile boolean shutdown;
public void close(){
shutdown=true;
}
public void doWork() {
while(!shutdown) {
System.out.println("safe...");
}
}
}
volatile变量如何立即可见?
当写一个volatile变量时,JVM会把该线程对应的工作内存中的共享变量值刷新到主内存中;
当读取一个volatile变量时,JMM会把该线程对应的工作内存置为无效。
volatile如何禁止重排优化
内存屏障(Memory Barrier)
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化
强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本
单例的双重检测实现
public class Stringleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//第一次检测
if (instance=null) {
//同步
synchronized (Stringleton.class){
if (instance == null) {
//多线程环境下可能会出现问题的地方
instance = new Singleton();
}
}
}
return instance;
}
}
多线程环境下还是有隐患,如何解决么!
public class Singleton {
//禁止指令重排优化
private volatile static Singleton instance;
private Singleton(){}
private static Singleton getInstance(){
//第一次检测
if (instance==null){
//同步
synchronized (Singleton.class){
if (instance == null) {
//多线程环境下可能会出现问题的地方
instance = new Singleton();
}
}
}
return instance;
}
}