从多线程模型复盘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前缀指令的作用:

  1. 将缓存行刷新回主存
  2. 保证lock前缀指令之前的指令都完成,并且实现了内存屏障的效果,禁止重排序

volatile的线程中特性的保障

经过上面的分析就能够得出volatile做出的保障:

  1. 原子性(单个指令)
  2. 有序性,lock前缀指令干的(内存屏障
  3. 可见性,lock前缀指令和MESI模型干的

讨论一下为什么volatile实现不了原子性

就拿最简单也最常见的场景来说- i ++

i ++ 就是两步操作,1.读取i; 2.将i设置成i + 1;

  1. A线程读取到了i=1,它知道自己要将i设置为2
  2. B线程此时也读取到了i=1,它知道自己要将i设置为2
  3. 然后就出现问题了,两个线程都将会把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的特性

posted @ 2022-11-29 09:40  azxx  阅读(12)  评论(0编辑  收藏  举报