多线程之Volatile笔记

cpu的高速缓存架构

待补充

多核cpu下如何保证数据的安全性

总线锁、#LOCK前缀指令加上缓存锁(MESI协议)

JMM模型

JVM为了兼容cpu处于不同系统时如何保证数据安全性而抽象出的模型
8个指令 use load read lock unlock write sotre assign

JVM中定义的4种内存屏障

loadload,loadstore,storestore,storeload。
4种内存屏障作用:待补充。
其中storeload包含了前三种内存屏障的作用。JVM中storeload方法内实际上是这样的一段代码:volatile (“lock; add1 0....实际上是使用了一个lock前缀指令,它是一个汇编层面的指令,作用是给处理器执行指令时置上LOCK#信号,置上LOCK#信号可以确保任何一个处理器能独占使用任何主内存,lock信号分两种,即总线锁和缓存锁。
总线锁:一个处理器对主内存进行读写操作时,不允许其他处理器访问,通过锁住总线(CPU与内存等硬件之前的通信需要经过总线)。
缓存锁:只锁特定的内存地址,当这个特定内存地址被锁定后,它就可以阻止其它的系统总线读取或修改这个内存地址。通过lock指令前缀再加上具体操作如(add)来实现。

Volatile作用

实现可见性,防止指令重排。
被volatile修饰的变量,在所有对它进行操作的指令上都会加上4种内存屏障。
首先禁止了编译器指令重排,其次会让JVM给处理器发送一条lock前缀指令使用缓存锁独占内存地址,在对这个变量进行写操作时,会即时写入主内存(从高速缓存写到主内存)。但是,其它处理器的缓存中存储的仍然是 “旧值” ,并不能保证可见性,因此,还要借助缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是否过期,当处理器发现自己缓存行对应的内存地址被修改时,就会设置当前缓存行为无效,需要对数据进行修改的时候会重新从主内存中加载。如此,便保证了可见性。

为什么不能保证原子性

如果对volatile修饰的变量进行一个复合操作,如i++。这个操作实际要分成三步:

volatile int i =1;
1.int a=i //从主内存中读取最新的i的值到工作内存
2.int b = a+1; 进行+1计算
3.int i = b; 在工作内存中将计算后的值赋予i,并及时写会主内存

假设有两个线程a,b。当它们并发执行i++操作时,它们的顺序可能会是:

a-1 //read-load从主内存中加载i值 i=1
a-2 //use使用i值并计算 累加器计算得出2
b-1 //此时线程a还没有将新值写回主内存,因此i=1
b-2 // 累加器计算值为2
a-3 //assign赋值,store-write写回主内存 i=2
b-3 //写回主内存 i=2

从上面过程可以看出,volatile能保证1,2,3的执行顺序,也能实现可见性(读取最新的i值,赋值刷新主内存),但无法保证原子性(1,2,3三个操作被分割执行了)。

你可能感兴趣的:(多线程之Volatile笔记)