原PPT文件:
内存可见性和CAS原理.key

Volatile保证内存可见性

JMM: java内存模型

image.png
JMM两条规定:
线程对共享变量的操作必须在线程私有工作内存中进行,不能直接从主存中读写
不同线程之间无法直接访问其他线程的私有工作内存中的变量,线程间变量值的传递需要通过主内存来完成

内存可见性

多个线程操作同一个变量,可确保写线程更新变量,其他读线程可以读取该变量的最新值。

什么原因造成内存不可见

image.png
线程的工作内存只是寄存器和cpu缓存的抽象描述。
CPU从主存读取数据的优先级是:寄存器 -> 缓存 -> RAM内存

  • 寄存器存储二进制指令
  • CPU缓存是位于CPU与内存之间的临时数据交换器,它的容量比内存小的多但是交换速度却比内存要快得多。用以缓解cpu和内存之间速度不匹配的矛盾

    高速缓存 CPUCache

    高速缓存是内存的部分拷贝,因为高速缓存速度快,把常用的数据放这里可以提高速度。高速缓存一般不能被程序直接更改,它由硬件自己处理。程序直接读写CPU的寄存器,来完成操作。

    多级缓存

    image.png

  • L1是最接近CPU的,它容量最小,速度最快,每个核上有两个L1 Cache, 一个存数据 L1d Cache, 一个存指令 L1i Cache;一个存数据一个存指令

  •  L2 Cache 更大一些,例如256K,速度要慢一些,一般情况下每个核上都有一个独立的L2 Cache;二级缓存就是一级缓存的缓冲器:一级缓存制造成本很高因此它的容量有限,二级缓存的作用就是存储那些CPU处理时需要用到、一级缓存又无法存储的数据。
  • L3 Cache是三级缓存中最大的一级,例如12MB,同时也是最慢的一级,在同一个CPU插槽之间的核共享一个L3 Cache。只有三级缓存是多核共享的。

级别越小的缓存,越接近CPU, 容量越小,但速度越快。
在多级缓存的架构中,如果缓存中找不到数据(cache miss),就会层层读取二级缓存三级缓存,一旦所有的缓存里都找不到对应的数据,就要去内存里寻址了。

MESI 缓存一致性协议

缓存行(Cache line): CPUCache中缓存存储数据的单元
image.png
image.png多个缓存区的缓存行持有主存count数据的copy

MESI协议只保证最终一致性而非强一致性。
既然有MESI协议,为什么非volatile变量还是内存不可见?
这是因为在CPU与高速缓存L1-Cache之间还有storeBuffer缓冲区 (cpu把它想要写入到主存的值写到缓存,然后继续去处理其他事情)。

示例代码:volatile保证可见性

image.png

i++非线程安全

count++在线程工作内存中经历了什么?

image.png

  1. read 从方法区读取静态变量count=0
  2. load copy静态变量count副本工作内存
  3. use 执行count++计算操作,此时操作的count是栈中副本
  4. assign 将1赋值给countcopy
  5. store
  6. write 将1写入到主内存count变量中

多线程模式下i++非线程安全图示

image.png
当线程1读出count=0并将count做+1操作但未及时写入主存时,线程二去主存读到count=0 (此时为脏读),同时也对count+1,此后两个线程先后提交count=1到主存,后提交者脏写,造成count终值错误。

volatile count++仍然非线程安全

image.png
image.png

综上:volatile保证了内存可见性,但没保证多指令的原子性,故非线程安全。

Volatile防止指令重排

image.png
指令重排序是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。

  1. volatile变量禁止指令重排序。针对volatile修饰的变量,在读写操作指令前后会插入内存屏障,指令重排序时不能把后面的指令重排序到内存屏

内存屏障

image.png

  • StoreStore
  • StoreLoad
  • LoadLoad
  • LoadStore

    1. 内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。<br />内存屏幕最初是硬件层面的概念,不同的硬件平台使用不同的方式 实现内存屏障,而jvm为了屏蔽硬件差异在软件层面完成了类似内存屏障的功能。<br />内存屏障保证volatile读和写之前或之后的普通读写指令不能被JVM调整。<br />因此,Volatile保证了有序性。
  1. Volatile保证了内存可见性和有序性,但没有保证多指令的原子性,故非线程安全。

CAS原理

CompareAndSwap

image.png
如果在write主存前,拿第一步read值和主存当前值做比较,如果相同才去设置,是不是可以保存数据安全了呢?

自旋锁

image.png
CAS利用CPU指令保证操作的原子性,以达到锁的效果
无限循环重试

AtomicInteger

image.png

CAS的缺点

1.CPU开销较大

2.不能保证多指令的原子性

3.ABA问题

ABA问题

image.pngimage.png
AtomicStampReference 给数据加一个版本号只有值和版本号完全与主存相同才允许设置。