Forehead
I was thinking about translating this article into English and Japanese, but not too sure if I need to do that; it will take one hour or two after all. Please shoot me an email if you feel that would work at: leileetcodekaggle@163.com
Prelude
总线风暴,缓存雪崩,容易联想到暴雪和鲁·高因。
Inspiration
自从装上了AMD的3990X,64核128线程的怪物配置之后: CineBench测试: PBO开启之后能达到3.8GHz!
前言
回归正题。Prerequisites: volatile
和JMM模型相关;volatile三大特性: 可见性,原子性,有序性;其中老生常谈:
- 原子性不能由
volatile
满足, 用integer举例出现AtomicInteger
,衍生出来的底层CAS还有ABA问题; - 有序性实际上就是”禁止指令Assembly重排”
不过这次主要讲的是, volatile
可以带来可见性。我之前以为这个没什么说的,就是描述“一个线程工作内存中对共享变量var
的修改对其它线程也是可见的”; 可是深究起来,tmd凭什么可以这样?为什么其它线程就可以看到某个线程对这个var
的修改?
我简单地写一下,因为不想写一大堆。
首先要知道,java代码编译之后变成class文件,也就是字节码;字节码经过JIT汇编得到Assembly汇编指令;这些指令就是机器可以识别的。(注: 后面如果要看总线锁情况下的反汇编得到的汇编指令, 需要安装hsdis/hotspot disassembler,点击这里
)
这里讨论多进程下的多线程之间的可见性问题,模型如下:
最下面的灰色的部分就是Main Memory
; 也就是说,CPU - L1缓存 - L2缓存 - L3缓存
和Main Memory
之间,是通过总线来进行数据交互的;这个虽然是常识,但是还是写一写避免有人不知道;
每个核上CPU工作,跑在不同CPU的线程,每个线程都有自己的工作内存。
JMM 8大原子操作
JMM 8大原子操作: load, read, use, assign, store, write, lock, unlock
.工作内存从主内存读,更改值之后再写会主内存,就是按照上面写的顺序进行的。
核心
volatile
保证可见性底层原理,就是java代码 -> class字节码文件 -> assembly汇编指令
这个过程中,在最后得到的汇编指令集前面加上lock
前缀;而lock
指令,能触发缓存锁定机制, 缓存锁定机制类似一个接口,具体落地实现是缓存一致性协议或者总线锁;不过因为总线锁之于缓存一致性协议,类似JDK1.2的synchronized
之于JDK1.6的synchronized
:过重导致效率低, 所以现在一般实现缓存锁定机制的都是缓存一致性协议。不过总线锁,可以用hsdis加一段VM Options
来看java代码对应的汇编指令:1
2
3
4
5-server
-Xcomp
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintAssembly
-XX:CompileCommand=compileonly,*VolatileVisibilitySample.refresh
缓存一致性协议MESI
MESI: Modified, Exclusive, Shared, Invalid
本质问题: 修改共享变量var
, thread A怎么知道thread B也在修改此变量?
答: 总线嗅探Bus Snooping
.
- 总线嗅探:thread A发现其它线程工作内存中也有
var
这个变量;E -> S
; - 某thread, e.g. thread A改变
var
成功,S -> M
; Bus write机制: 总线写入:通知其它所有之前总线嗅探过的线程,var: M
了,所以你们要放弃之前从Main Memory
获得的var
的值,重新去Main Memory
获得新值; - 当然,
var
修改之后,thread A会尽快把数值store
到总线,再总线write
到Main Memory
(都是JIT 8大数据原子操作
(JIT把bytecode翻译成Assembly), 便于其它线程从主内存获得最新值。
总线风暴,这里不写了。