锁和volatile
锁的特性:原子性、可见性
volatile的特性:可见性
原子性:一次只允许一个线程使用共享数据。
可见性:保证共享变量对所有线程的可见性,当一个线程修改了共享变量的值,新值能立即同步到主内存,其它线程每次使用前立即从主内存刷新。
volatile的作用,可见性 + 禁止指令重排序优化。
Happens-Before六大规则
作用:某个线程修改的变量何时对其他线程可见。
1、程序的顺序性原则
单线程程序前面对某个变量的修改一定对后续操作可见。
2、volatile变量规则
对于一个volatile变量,写操作 Happens-Before 后续的读操作。
3、传递性规则
A Happens-Before B,且 B Happens-Before C,那么A Happens-Before C。
4、管程(synchronized)中锁的规则
对于一个锁,解锁 Happens-Before 后续加锁,所以synchronized加锁解锁会刷新内存。
5、线程start()规则
子线程start()后,子线程中能看到start()前的变量。
6、线程join()操作
主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。
重排序
1、编译器重排序。编译器在不改变单线程程序语义的前提下,可以对语句乱序执行。
2、指令重排序。为了提高执行效率,CPU可以乱序执行。
3、缓存的读和写,看上去像是在乱序执行。
内存屏障
【内容有误,待改】
(1)内存屏障的作用:
- 阻止屏障两侧的指令重排序;
- 强制把主存数据更新到本地缓存,和把写缓存区数据写回主存。
(2)两种屏障:
- Load Barrier:在指令前插入Load Barrier,加载数据时会强制从主存加载数据。
- Store Barrier:在指令后插入Store Barrier,会让写入缓存中的数据更新写入主存。
(3)java的内存屏障更细致,分为四种:
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barrier | Load1;LoadLoad;Load2 | 确保Load1装载 先于Load2及后续装载 |
StoreStore Barrier | Store1;StoreStore;Store2 | 确保Store1写入 先于Store2及后续写入 |
LoadStore Barrier | Load1;LoadStore;Store2 | 确保Load1装载 先于Store2及后续写入 |
StoreLoad Barrier | Store1;StoreLoad;Load2 | 确保Store1写入 先于Load2及后续装载 它会使屏障前的(加载和写入指令)完成后,才执行屏障之后的(加载和写入指令)。 这个屏障是个万能屏障,兼具其它三种内存屏障的功能,它的开销是四种屏障中最大的。 |
(4)volatile 的内存屏障策略非常严格保守,任何指令都不能重排序:
- 在每个volatile
写操作前
插入StoreStore
屏障,在写操作后
插入StoreLoad
屏障; - 在每个volatile
读操作前
插入LoadLoad
屏障,在读操作后
插入LoadStore
屏障;
上面概括为:SS,写,SL、LL,读,LS
也可以这样:LS,写,SS、SL,读,LL