内存可见性:所有线程都能看到共享内存的最新状态。每次读取前必须先从主内存刷新最新的值。每次写入后必须立即同步回主内存当中。
volatie 轻量级的 Synchronized , 可以保证共享变量的可见性。也就是说,一个线程能够读取到另外一个线程修改后的值。但是比 synchronized 开销更小。
volatile关键字提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
并发编程中, 通常会遇到三个问题:原子性问题,可见性问题,有序性问题。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
线程A 修改一个普通变量的值,然后向主内存进行回写,另外一个线程在线程A回写完之后再对主内存进行读取操作,新变量才会对线程 B 可见。如果读取的不是写完之后的变量,说明新变量对线程B不可见。
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
Java 内存模式直接保证的原子性变量操作:read , load, asign , use ,store和 write 这 6个。大致可以认为,基本数据类型的访问,读写都具备原子性。
Java 内存模型可以通过 synchronized 保证原子性。通过monitorenter 和 monitorexit 保证原子性。
有序性:即程序执行的顺序按照代码的先后顺序执行。
Java程序中天然的有序性可以总结为:
不同计算机操作系统的内存模型是不一样的,这样就需要统一的规范 Java 内存模型(Java Memory Model,JMM).
Java 内存区域:虚拟机栈,本地方法栈,堆,方法区,程序计数器,直接内存。
Java 内存区域和内存模型要有所区别
内存模型抽象结构示意图
Java通过几种原子操作完成工作内存和主内存的交互:
volatile的特殊规则就是:
所以,使用volatile变量能够保证:
每次读取前必须先从主内存刷新最新的值。每次写入后必须立即同步回主内存当中也就是说,volatile关键字修饰的变量看到的随时是当前变量的最新值。
基于保守策略的JMM内存屏障插入策略:
对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。
volatile不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}