18.3.3 volatile关键字与内存可见性
1. volatile基础概念
1.1 volatile的作用和特性
public class VolatileBasics {
// 普通变量 - 可能存在可见性问题
private boolean normalFlag = false;
// volatile变量 - 保证可见性
private volatile boolean volatileFlag = false;
public void demonstrateVisibility() {
System.out.println("=== volatile可见性演示 ===");
// 线程1:修改标志位
Thread writer = new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println("Writer: 设置volatile标志为true");
volatileFlag = true;
Thread.sleep(1000);
System.out.println("Writer: 设置normal标志为true");
normalFlag = true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 线程2:读取标志位
Thread reader = new Thread(() -> {
while (!volatileFlag) {
// 等待volatile标志变为true
}
System.out.println("Reader: 检测到volatile标志变化");
if (normalFlag) {
System.out.println("Reader: 也看到了normal标志变化");
} else {
System.out.println("Reader: 没有看到normal标志变化");
}
});
writer.start();
reader.start();
}
}
1.2 volatile的三大特性
可见性 |
一个线程修改volatile变量,其他线程立即可见 |
解决缓存一致性问题 |
有序性 |
禁止指令重排序优化 |
保证代码执行顺序 |
原子性 |
对单个volatile变量的读写是原子的 |
64位变量的读写原子性 |
2. Java内存模型与可见性
2.1 JMM内存模型详解
public class JavaMemoryModel {
private int sharedVariable = 0;
private volatile int volatileVariable = 0;
/*
* Java内存模型(JMM):
*
* 1. 主内存(Main Memory):所有变量的主存储区域
* 2. 工作内存(Working Memory):每个线程的私有内存区域
* 3. 内存交互操作:
* - read/load: 从主内存读取到工作内存
* - use/assign: 工作内存与执行引擎交互
* - store/write: 从工作内存写回主内存
*/
public void demonstrateMemoryModel() {
ExecutorService executor = Executors.newFixedThreadPool(4);
// 写线程
for (int i = 0; i < 2; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
sharedVariable = threadId * 1000 + j;
volatileVariable = threadId * 1000 + j;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
}
executor.shutdown();
}
}
3. volatile与指令重排序
3.1 指令重排序问题
public class InstructionReordering {
private int a = 0;
private boolean flag = false;
private volatile boolean volatileFlag = false;
/*
* 指令重排序类型:
* 1. 编译器重排序:编译器优化
* 2. 指令级并行重排序:CPU指令级并行
* 3. 内存系统重排序:缓存和读写缓冲区
*/
public void demonstrateReordering() {
for (int i = 0; i < 1000; i++) {
a = 0;
flag = false;
volatileFlag = false;
testNormalVariableReordering();
testVolatileVariableReordering();
}
}
private void testNormalVariableReordering() {
Thread writer = new Thread(() -> {
a = 1; // 可能被重排序
flag = true;
});
Thread reader = new Thread(() -> {
if (flag) {
if (a == 0) { // 可能看到重排序结果
System.out.println("检测到普通变量重排序");
}
}
});
writer.start();
reader.start();
}
private void testVolatileVariableReordering() {
Thread writer = new Thread(() -> {
a = 1; // 不会被重排序到volatile写之后
volatileFlag = true; // volatile写
});
Thread reader = new Thread(() -> {
if (volatileFlag) { // volatile读
// 由于happens-before规则,a=1必定可见
assert a == 1;
}
});
writer.start();
reader.start();
}
}
3.2 内存屏障机制
public class MemoryBarrier {
private int x = 0;
private volatile int volatileVar = 0;
/*
* 内存屏障类型:
* 1. LoadLoad屏障:确保Load1先于Load2
* 2. StoreStore屏障:确保Store1先于Store2
* 3. LoadStore屏障:确保Load1先于Store2
* 4. StoreLoad屏障:确保Store1先于Load2
*/
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经
查看22道真题和解析