保证可见性,
总结:volatile作用
1、可以保证可见性、防止内存指令重排序
2、lock(汇编) ->缓存锁(mesi协议(缓存一致性协议))
3、内存屏障(读屏障,写屏障,全屏障)(在适当位置加入屏障,使指向执行不能重排序,强制cpu将数据写入内存(不存储在cpu高速缓存),这样其他cpu就能从内存中读取到数据),使用volatile会自动生成内容屏障
为什么有可见性问题存在?
1、从硬件层面
cup核心有三级缓存l1,l2,l3,其中l1和l2是单个cpu核心独自的,l3是多cpu核心共享的
因为多核心cpu高速缓存的存在会导致缓存一致性问题,
总线锁(访问数据互斥,这种降低了性能),缓存锁(在总线锁之后的优化方式,当数据在当前cpu高速缓存内,才使用缓存锁,通过mesi缓存一致性协议实现数据一致性)
mesi会引入stroeBufferes,优化了cpu核心之间通信导致的阻塞等待问题,提高了性能,同时也导致了指令重排序问题(例:当cup要执行两条指令a=1;b=0,若a为多cpu共享数据时,b为当前cpu独占,a=1会先写入stroeBufferes,同时通知其他cpu失效a的值,在a写入stroeBufferes同时b已经在当前cpu执行,这样就会导致b=0优先于a=1执行)
优先使用缓存锁,cpu不满足条件会使用总线锁(x86之前只支持总线锁)
2、软件层面
java内存模型(JMM),根据不同的平台(window,linux...)生成不同的内存屏障的指令,
JMM抽象了cpu内存模型,并提供了一些高级指令(volatile、final...),让开发者去解决指令重排序问题
public class DCLClass {
public static volatile DCLClass dclClass;
public DCLClass() {
}
public DCLClass getInstance(){
if (dclClass == null){
synchronized (dclClass){
if (dclClass == null){
//new对象会执行三个指令,若不加volatile,会导致指令重排,多线程情况下
//可能会返回一个不完全对象,dclClass == null判断不成功,导致生成多个实例
dclClass = new DCLClass();
}
}
}
return dclClass;
}
}
Happens-Before模型(描述的是一个可见性问题):
happens-before原则定义如下:
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
下面是happens-before原则规则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
网友评论