一、三大性质

原子性、有序性、可见性

java内存模型中定义了8种操作都是原子的:lock、unlock、read、load、use、assign、store、write

如果我们需要更大范围的原子性操作就可以使用lock和unlock原子操作。尽管jvm没有把lock和unlock开放给我们使用,但jvm以更高层次的指令monitorenter和monitorexit指令开放给我们使用,反应到java代码中就是---synchronized关键字,也就是说synchronized满足原子性volatile不满足原子性。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

synchronized和volatiled都满足有序性。

synchronized和volatiled都满足可见性。

二、JMM

主内存对应PC的内存,Java线程工作内存对应CPU的高速缓存

volatile关键字的一些理解_内存

三、volatile关键字

作用:

1、volatile可以保证可见性,A线程修改了volatile值,B线程立马可见。

2、volatile不能保证原子性:

类似于i++操作,涉及到读取i到栈顶,提取加1,结果压栈,写回主内存。

多个线程进行++,使用volatile还是会有线程安全问题。

一个线程++后,可以立马写回主内存,其他cpu执行其他线程,cpu高速缓存内的i值也会失效,而后更新。

但是其他线程读取到栈顶的i值已经不是最新的了,结果还是会错误。

所以volatile可以用在多线程对变量原子性操作的场景,来确保线程安全。

如果是非原子性操作,可以考虑AtomicInteger或者synchronized。

volatile可见性的实现原理:

使用了内存屏障,来屏蔽操作系统优化作出的某些不安全的指令重排。

内存屏障的理解:

1、后续的Load肯定不能重排序到我Store前面。

2、Store的时候,Store的对象肯定都初始化完成了。

比如StoreLoad 屏障

执行顺序: Store1—> StoreLoad—>Load2

确保Load2和后续的Load指令读取之前,Store1的数据对其他处理器是可见的。

具体是通过一个lock 寄存器+0(空操作)指令实现的,这个操作会将变量修改store,write主内存,并使其他cpu高速缓存内该变量失效,重新拉取。相当于分布式缓存的更新策略。先更新主存,再删除缓存。

如果是单cpu,那么不不需要内存屏障来禁止指令重排,因为指令重排,还是会保证语义正确,不会影响最终结果,产生不安全的原因多核多线程。

volatile双锁单例中的应用:

public class Singleton {

public static volatile Singleton instance;

public static Singleton getInstance() {

if (instance == null) {

synchronized (instance) {

if (instance == null) {

instance = new Singleton();

}

}

}

return instance;

}

}

单例变量如果不用volatile修饰:

new Singleton(),执行改行代码需要三个步骤,①开辟内存空间②初始化数据③刷新实例回主存。现在,线程A进入双重非空判断,创建实例,如果操作系统指令重排,使②③颠倒,那么实例先刷新回主存,还没有初始化,这时候线程B进入第一重非空判断,不为空,直接返回了不完整的实例,这就出现了线程安全问题。

所以用volatile修饰以后,内存屏障禁止指令重排,首先必须完全初始化,才会刷新回主存,其次其他线程的load操作需要在store之后。