bilibili-Java并发学习笔记8 volatile
基于 java 1.8.0
P33_volatile关键字作用与锁的关系深入详解
volatile 的作用:
- 实现
long
/double
类型变量的原子操作- volatile double a = 1.0;
- jdk1.5 AtomicLong
- 防止指令重排序
- 实现变量的可见性
- JMM
- 使用 volatile 时,不再会从寄存器中获取缓存值,而是直接从内存中获取;
volatile 与 锁 相似的特点:
- 确保变量的内存可见性
- 防止指令重排序
volatile 与 锁 的区别:
- volatile 可以确保对变量
写操作的原子性
,但不具备排他性(互斥性); - 使用锁可能会导致线程的上下文切换(内核态与用户态的切换),但 volatile 并不会出现这种情况;
不当用法:
- volatile int a = b + 2;
- volatile int a = a++;
- volatile Date date = new Date();
适合用法:
- volatile int a = 7;
- volatile boolean flag = false;
如果要实现 volatile 写操作的原子性,那么在等号右侧的赋值变量中就不能出现被多线程所共享的变量,哪怕这个变量也是 volatile 修饰的也不行;
P34_volatile与内存屏障的重要语义详细分析
防止指令重排序与实现变量的可见性都是通过一种手段来实现的:内存屏障
(memory barrier);
package new_package.thread.p33;
public class VolatileTest {
int a = 1;
String s = "hello";
// 内存屏障 (Release Barrier,释放屏障): 防止下面的 volatile 与上面的操作的指令重排序
volatile boolean v = false;//写入操作
// 内存屏障 (Store Barrier,存储屏障): 刷新处理器的缓存,可以确保该存储屏障之前的一切操作所生成的结果对于其他处理器来说都可见了
String s2 = s + "hello";
// ---
// 内存屏障 (Load Barrier,加载屏障): 可以刷新处理器的缓存,同步其他处理器对该 volatile 变量的修改结果
boolean v1 = v; // 读取操作
// 内存屏障 (Acquire Barrier,获取屏障): 可以防止之前的 volatile 读取操作与之后的所有操作语句的指令重排序
String h = "world";
// 对于 volatile 变量的读写操作,本质上都是通过内存屏障来实现的。
// 内存屏障兼备了两方面的能力:1.防止指令重排序,2.实现变量内存的可见性
// 对于读操作 :volatile 可以确保该操作与之后的所有读写操作都不会进行指令重排序;
// 对于写操作 :volatile 可以确保该操作与之前的所有读写操作都不会进行指令重排序;
}
package new_package.thread.p33;
import java.util.Date;
public class SynchronizedTest {
// 锁同样具备变量内存可见性与防止指令重排序的功能
synchronized void func() {
// monitorenter
// 内存屏障 (Acquire Barrier,获取屏障)
// 逻辑代码
Date date = new Date();
String name = "world";
// 内存屏障 (Release Barrier,释放屏障)
// monitorexit
}
}
P35_JMM与happen-before规则深入详解
- 变量的原子性问题
- 变量的可见性问题
- 变量修改的时序性问题
happen-brfore 重要规则:
- 顺序执行规则(限定在单个线程上的):该线程的每个动作都 happen-before 之后的动作;
- 隐式锁(monitor)规则 : unlock 是 happen-before lock ,之前的线程对于同步代码块的所有执行结果对于后续获取锁的线程来说都是可见的;
- volatile 读写规则 : 对于一个 volatile 变量的写操作一定会 happen-before 后续对该变量的读操作;
- 多线程的启动规则 : Thread 对象的 start 方法 happen-before 该线程 run 方法中的任何一个动作,包括在其中启动的任何子线程;
- 多线程的终止规则 : 一个线程启动了一个子线程,并且调用了子线程的 join 方法等到其结束,那么当子线程结束后,父线程的接下来的所有操作都可以看到子线程 run 方法中的执行结果;
- 线程的中断规则 : 可以调用 interrupt 方法来中断线程,这个调用 happen-before 对该线程中断的检查(isInterrupted);