从多线程模型复盘volatile
从多线程模型复盘volatile
volatile的存在,我猜就是为了适应JMM这种线程之间有本地内存和有共享内存的模型。解决了数据的可见性问题和有序性问题。
volatile的效果
当两个线程使用到了同一个变量的时候,他们都会在自己线程的本地内存中有一个变量的副本,而这个副本是自己独享的,所以对其的所有操作是线程间隔离的。volatile的效果就是让他们不再被隔离。
MESI模型
MESI:
M:modified 被修改了 E:exclusived 被独占的 S:shared 被共享的 I:isolated 被隔离的
当一个线程对被volatile修饰的变量A进行修改的时候,会将其缓存中的缓存行设置为M,表示被修改了,然后这时候嗅探机制发挥作用,其他线程进行感知,将其原本的S修改为I。之后再从这个I的缓存行读取的时候,就会从主存刷新。
volatile的底层原理
volatile的原理在如今比较简单,x86架构下就是lock前缀指令去保障着volatile的。
lock前缀指令的作用:
将缓存行刷新回主存 保证lock前缀指令之前的指令都完成,并且实现了内存屏障的效果,禁止重排序
volatile的线程中特性的保障
经过上面的分析就能够得出volatile做出的保障:
原子性(单个指令) 有序性,lock前缀指令干的(内存屏障 可见性,lock前缀指令和MESI模型干的
讨论一下为什么volatile实现不了原子性
就拿最简单也最常见的场景来说- i ++
i ++ 就是两步操作,1.读取i; 2.将i设置成i + 1;
A线程读取到了i=1,它知道自己要将i设置为2 B线程此时也读取到了i=1,它知道自己要将i设置为2 然后就出现问题了,两个线程都将会把i设置为2.
要解决这么一种对于共享变量的获取的情况,就让我们的加锁来做,或者说用操作系统给我们提供出来的CAS原子操作。
CAS
CAS也叫compare and swap,他会对内存中的值和它保存的值进行对比,如果发现和它保存的值一样才会进行修改,这种操作是原子性的。
如上图所示,如果有了CAS之后,我们就可以有这么一种符合一致性的结果。
CAS的底层原理
首先CAS一定是底层提供出来的一个原子操作,但我们看到的CAS是经过了封装后的,要在JVM层面实现的CAS,为了适应JMM模型也有一些改变,比如它也要有可见性和有序性。 如下图,CAS在JVM中的实现是Atomic::cmpxchg。 它会先判断我们的当前机器有无多核,如果有多核的话,他会为我们添加上lock前缀指令,让我们能够保持有序性和可见性。 之后的就是参照底层的方法提供参数。
CAS + 自旋 实现乐观锁
CAS的问题
ABA问题
由于CAS只会进行比较值,所以它没办法感知在它读和写的这段时间内,有无人对值进行了操作,最终变回了原值。
ABA的解决就是进行对版本的感知,加入版本号这类的东西。如AtomicStampedReference就进行了实现,每一次的修改都会改动版本号。
CAS自旋对CPU的占用问题
这就是一个本身用机器性能换效率的事情,无法解决
只能对单个变量进行操作
这也是CAS的特性