java内存模型的主要目标就是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的细节。 java内存模型中规定所有的变量都存储在主内存中,每条线程都有自己的工作内存,线程的工作内存中存储了该线程所使用的变量的从主内存中拷贝的副本。线程对于变量的读写都必须在工作内存中进行。而不能直接读写主内存中的变量。同时 本线程的工作内存的变量也无法被其他线程直接访问。必须通过主内存进行。 对于普通共享变量,线程A将变量修改后,体现在线程的工作内存,在尚未同步到主内存时,若线程B使用此变量,村主内存中获取到的事修改前的值,便发生了共享变量值的不一致,这就是线程变量的可见性问题。
- 当对volatile便令执行写操作后,JMM会把工作内存中的最新变量值强制刷新到主内存。
- 写操作会导致其他线程中的缓存失效
- 一个线程写入变量a后所有访问该变量的线程都可以拿到a最新的值。
- 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入
- 保障操作的执行顺序
- 影响某些数据(或者是某条指令的执行结果)的内存可见性。
sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见
lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。
mfence:即全能屏障(modify/mix Barrier ),兼具sfence和lfence的功能
lock 前缀:lock不是内存屏障,而是一种锁。执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个 Write-Barrier(写入屏障)将刷出所有在 Barrier 之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。
- 三个值 A持有的旧值,新值,内存实际值 当且仅当持有的值与内存实际值相同时,把新值赋值给内存,并返回true。CAS操作基于CPU提供的原子操作指令实现。对于Intel X86处理器,可通过在汇编指令前增加LOCK前缀来锁定系统总线,使系统总线在汇编指令执行时无法访问相应的内存地址。而各个编译器根据这个特点实现了各自的原子操作函数。
- ABA问题
在a访问内存前,B已经将内存中的值进行了改变,碰巧改变后的值与a自己持有的值相同,此时满足CAS,但实际使用时可能会出问题。有的需求,比如CAS,只注重头和尾,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改. 解决:每个操作内存中的变量值的线程需要加入自己的标识,此标识可以是时间戳或者版本号,java提供了AtomicStampedReference
来解决此问题,该类型在进行CAS时,不光比较值,还比较了时间戳。