volatile
保障 可见性 。
有序性(禁止指令重排序,happens-before原则)。
不能保证原子性。
特例: volatile 在修饰 long 和double的时候 是64位的,对这个变量进行读写的时候 jvm规范要求 实现jvm的hotspot必须保障他是原子性的。
如何保障可见性:
MESI 缓存一致性协议 + lock指令保障可见性的。
用于volatile修饰的变量进行写操作的时候,jvm会发送一条lock指令给cpu,cpu在计算完毕后会立刻将值写入到主存中。
MESI缓存一致性协议(缓存行的四种状态),各个CPU都会对总线进行嗅探,如果发现被修改,则会将本地的缓存行里的数据置为失效,需要的时候会重新从主存拉取数据。
什么是cpu乱序执行?
cpu为了提高效率,会同时执行多条无关的指令。例如一条指令花费时间是100ms,在等待的期间,cpu会同时去执行其他啊多个耗费10ms的指令。
什么是happens-before原则?
jvm规范要求实现jvm的虚拟机例如hotspot里的指令按照规范要求的顺序执行。
硬件级别:
flush refresh.
指令重排序:
JIT编译器对创建对象的指令重排。DCL 不用volatile会发生空指针。(详情见创建一个对象的过程。)
实际场景:
用于一个boolean变量作为开关,去进行控制其他线程的运行,例如关闭资源等。
高速缓存(L1,L2,L3越接近处理器的速度越快)的数据结构
拉链散列表
1、高速缓存的底层数据结构是一个拉链散列表,也就是多个bucket。 2、每个bucket挂了很多的cache entry,由tag(对应主存中的地址),cache line(缓存数据),flag(缓存行状态)组成。 3、在处理器对高速缓存进行读写的时候,会通过变量名执行一个内存地址解码的操作,解码出三个东西:index(哪个bucket)、tag(定位到bucket中具体的一个cacke entry)、offset(找到在cache entry中cache line的相对位置) 4、如果处理器从高速缓存中读不到对应的数据,就会去主存或者其他处理器的高速缓存中读取放到高速缓存中。 5、高速缓存是分层的,L1、L2、L3越靠前的读写速度越快。
MESI原理
硬件层面的原理 -> MESI协议在硬件层面运行的原理 -> 这套原理为何会导致可见性和有序性的问题 -> 各种内存屏障是如何在硬件层面解决可见性和有序性的问题 -> volatile和synchroized是如何加各种内存屏障来分别保证可见性和有序性的
硬件层面 MESI原理图:
为了提高硬件层面的读写效率,引入 写缓冲器和 无效队列。
过程: 处理器0写一条数据到写缓冲器中,然后发送invalidate消息。就认为写入数据成功,处理器异步嗅探到所有invalidate ack消息就会把写缓冲器里的数据写入到高速缓存(过程为 flag= e ,然后flag=m).
处理器1收到invalidate消息,将消息入队,然后返回invalidate 消息ack.就认为已经将这条数据置为无效。(异步消费 将缓存行 flag=i)
可见性:
1.处理器0在写入数据到写缓冲区的的时候,数据还没有刷到主存。还没有发送invalidate消息,其他处理器读的还是老的数据。
2.处理器1接收到invalidate消息,但是还没有在消息队列里消费的时候,处理器1读的还是旧的缓存数据。
指令重排序:
1.storestore重排序:
指令1 : 缓存行flag=S ,处理器要修改这条缓存行里的数据的时候需要先将数据写入写缓冲器中,然后发送一条invalidate指令消息,直到总线返回所有的 invalidate消息的ack,然后从写缓冲器取出数据,对缓存行进行加锁,flag=E ,修改其中的值,flag=M。
指令2:缓存行 flag=M ,可以直接修改,不需要上述步骤,则执行结果是 指令2 先执行 指令1后执行。
2.storeload重排:
指令1: 写指令到 写缓冲器中。
指令2:指令2读出来的是旧的数据。
指令1:写入到主存中
导致指令2先于指令1执行。
如何通过内存屏障来解决可见性和指令重排的问题?
1.flush操作 : 用store屏障,在写数据的时候,会将写数据的整个流程执行完(包括等待invalidate ack 和写入数据到告诉缓存)在去执行别的操作 。
2. refresh操作:加了load屏障,在读数据的时候发现无效队列里有值,会将其刷到告诉缓存中,将缓存行置为无效。
3. 禁止指令重排序:
个人理解: 就是把这些指令进行类似串行化,在来执行。
volatile:
如何保障有序性:
通过内存屏障。
storestoreBarrier__volatile写操作__storeLoadBarrier
loadloadBarrier_volatile读操作_loadStoreBarrier
保障可见性: 通过内存屏障会有 flush和refresh操作。
不能保障原子性。
synchronized
通过加锁保障原子性。
通过load,store保障与外部的可见性。refresh,flush操作
通过aquire,release屏障来保障有序性(与外部的有序性)。
DCL必须加volatile是因为防止构造器内部进行指令重排序。