第5部分:Java内存模型与可见性
核心目标
掌握并发问题的底层根源。
1. Java内存模型(JMM)详解
JMM基本概念
Java内存模型(Java Memory Model,JMM)是Java虚拟机规范中定义的一种内存访问规范,它定义了程序中各个变量的访问规则,以及在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。
JMM结构图
┌─────────────────────────────────────────────────────────────┐
│ JVM内存结构 │
├─────────────────────────────────────────────────────────────┤
│ 主内存 (Main Memory) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 实例变量 │ │ 静态变量 │ │ 数组元素 │ │
│ │ (堆内存) │ │ (方法区) │ │ (堆内存) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 线程间共享的变量 │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 线程1工作内存 线程2工作内存 线程N工作内存 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 变量副本 │ │ 变量副本 │ │ 变量副本 │ │
│ │ 局部变量 │ │ 局部变量 │ │ 局部变量 │ │
│ │ 程序计数器 │ │ 程序计数器 │ │ 程序计数器 │ │
│ │ 操作数栈 │ │ 操作数栈 │ │ 操作数栈 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
内存交互操作
public class JMMExample {
private int sharedVariable = 0;
public void thread1() {
// 1. read: 从主内存读取sharedVariable到工作内存
// 2. load: 将读取的值放入工作内存的变量副本
// 3. use: 使用变量副本进行计算
int temp = sharedVariable;
temp = temp + 1;
// 4. assign: 将计算结果赋值给工作内存的变量副本
sharedVariable = temp;
// 5. store: 将工作内存的变量副本传送到主内存
// 6. write: 将store操作从工作内存得到的变量值放入主内存的变量中
}
public void thread2() {
// 同样的8个操作步骤
int temp = sharedVariable;
temp = temp + 1;
sharedVariable = temp;
}
}
2. 主内存与工作内存关系
内存层次结构
public class MemoryHierarchy {
// 主内存中的变量
private static int staticVar = 0; // 方法区(主内存)
private int instanceVar = 0; // 堆内存(主内存)
private int[] array = new int[10]; // 堆内存(主内存)
public void memoryAccess() {
// 局部变量在工作内存中
int localVar = 0; // 虚拟机栈(工作内存)
// 访问主内存变量
staticVar++; // 需要从主内存读取,修改后写回
instanceVar++; // 需要从主内存读取,修改后写回
array[0] = 1; // 需要从主内存读取,修改后写回
// 局部变量操作
localVar++; // 直接在工作内存中操作
}
}
内存可见性问题
public class VisibilityProblem {
private boolean flag = false;
private int count = 0;
// 线程1执行
public void thread1() {
count = 1; // 1. 写入工作内存
flag = true; // 2. 写入工作内存
// 注意:工作内存的修改可能不会立即同步到主内存
}
// 线程2执行
public void thread2() {
if (flag) { // 3. 从主内存读取flag(可能是旧值)
int temp = count; // 4. 从主内存读取count(可能是旧值)
System.out.println("Count: " + temp); // 可能输出0
}
}
}
内存同步机制
public class MemorySynchronization {
private int value = 0;
private volatile boolean ready = false;
// 使用synchronized保证内存同步
public synchronized void synchronizedMethod() {
value = 42;
ready = true;
// synchronized块结束时会强制将工作内存刷新到主内存
}
public synchronized int getValue() {
// synchronized块开始时会从主内存重新读取变量
return value;
}
// 使用volatile保证内存同步
public void volatileMethod() {
value = 42;
ready = true; // volatile写操作会立即刷新到主内存
}
public int getVolatileValue() {
if (ready) { // volatile读操作会从主内存读取最新值
return value;
}
return 0;
}
}
3. Happens-Before规则
Happens-Before定义
Happens-Before是JMM中定义的一个偏序关系,用于描述两个操作之间的可见性关系。如果操作A happens-before 操作B,那么操作A的结果对操作B可见。
基本规则
public class HappensBeforeRules {
private int x = 0;
private int y = 0;
private volatile boolean flag = false;
// 规则1:程序顺序规则
public void programOrderRule() {
x = 1; // 操作1
y = 2; // 操作2
// 操作1 happens-before 操作2
}
// 规则2:volatile变量规则
public void volatileRule() {
x = 1; // 操作1
flag = true; // 操作2 (volatile写)
// 操作1 happens-before 操作2
// 在另一个线程中
if (flag) { // 操作3 (volatile读)
int temp = x; // 操作4
// 操作2 happens-before 操作3
// 操作1 happens-before 操作4 (通过传递性)
}
}
// 规则3:传递性规则
public void transitivityRule() {
// 如果 A happens-before B,B happens-before C
// 那么 A happens-before C
}
// 规则4:锁规则
public synchronized void lockRule() {
x = 1; // 操作1
// 解锁操作 happens-before 后续的加锁操作
}
public synchronized void anotherLockMethod() {
int temp = x; // 操作2
// 操作1 happens-before 操作2
}
}
线程启动规则
public class ThreadStartRule {
private int x = 0;
public void threadStartExample() {
x = 42; // 操作1
Thread thread = new Thread(() -> {
int temp = x; // 操作2
System.out.println("x = " + temp); // 输出42
});
thread.start(); // 操作3
// 操作1 happens-before 操作3
// 操作3 happens-before 操作2
// 因此操作1 happens-before 操作2
}
}
线程终止规则
public class ThreadTerminationRule {
private int result = 0;
public void threadTerminationExample() throws InterruptedException {
Thread thread = new Thread(() -> {
result = 42; // 操作1
});
thread.start();
thread.join(); // 操作2
int temp = result; // 操作3
System.out.println("result = " + temp); // 输出42
// 操作1 happens-before 操作2
// 操作2 happens-before 操作3
// 因此操作1 happens-before 操作3
}
}
中断规则
public class InterruptRule {
private int x = 0;
public void interruptExample() throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
x = 42; // 操作1
Thread.currentThread().interrupt();
}
});
thread.start();
thread.interrupt(); // 操作2
thread.join();
int temp = x; // 操作3
// 操作2 happens-before 操作1
// 操作1 happens-before 操作3
}
}
4. volatile关键字的语义与限制
volatile的基本语义
public class VolatileSemantics {
private volatile boolean flag = false;
private volatile int count = 0;
// 1. 可见性保证
public void visibilityExample() {
// 线程1
count = 1; // volatile写,立即刷新到主内存
flag = true; // volatile写,立即刷新到主内存
// 线程2
if (flag) { // volatile读,从主内存读取最新值
int temp = count; // 能读取到最新的count值
}
}
// 2. 禁止重排序
public void reorderingExample() {
int a = 1;
int b = 2;
flag = true; // volatile写,不能与前面的操作重排序
int c = a + b; // 不能与volatile写重排序
}
}
volatile的内存屏障
public class VolatileMemoryBarrier {
private int x = 0;
private int y = 0;
private volatile boolean flag = false;
public void memoryBarrierExample() {
// 在volatile写之前插入StoreStore屏障
x = 1; // 普通写
y = 2; // 普通写
// StoreStore屏障
flag = true; // volatile写
// StoreLoad屏障
// 在volatile读之后插入LoadLoad和LoadStore屏障
if (flag) { // volatile读
// LoadLoad屏障
// LoadStore屏障
int temp = x; // 普通读
int temp2 = y; // 普通读
}
}
}
volatile的限制
public class VolatileLimitations {
private volatile int count = 0;
// 1. volatile不能保证原子性
public void atomicityProblem() {
// 这个操作不是原子的
count++; // 等价于 count = count + 1
// 包含:读取count、计算count+1、写入count三个步骤
// 多个线程同时执行时仍可能出现竞态条件
}
// 2. volatile不能保证复合操作的原子性
public void compoundOperationProblem() {
// 这个操作不是原子的
count = count * 2 + 1;
// 包含多个步骤,volatile无法保证整个操作的原子性
}
// 3. volatile的正确使用场景
private volatile boolean shutdown = false;
public void correctVolatileUsage() {
// 作为状态标志
while (!shutdown) {
// 执行任务
}
}
public void shutdown() {
shutdown = true; // 简单的状态改变
}
// 4. 单例模式中的volatile使用
private static volatile VolatileLimitations instance;
public static VolatileLimitations getInstance() {
if (instance == null) { // 第一次检查
synchronized (VolatileLimitations.class) {
if (instance == null) { // 第二次检查
instance = new VolatileLimitations();
}
}
}
return instance;
}
}
volatile vs synchronized
public class VolatileVsSynchronized {
private volatile int volatileCount = 0;
private int synchronizedCount = 0;
// volatile方式
public void volatileIncrement() {
volatileCount++; // 不是原子操作
}
// synchronized方式
public synchronized void synchronizedIncrement() {
synchronizedCount++; // 原子操作
}
// 性能对比
public void performanceComparison() {
long start = System.currentTimeMillis();
// volatile操作
for (int i = 0; i < 1000000; i++) {
volatileIncrement();
}
long volatileTime = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
// synchronized操作
for (int i = 0; i < 1000000; i++) {
synchronizedIncrement();
}
long synchronizedTime = System.currentTimeMillis() - start;
System.out.println("Volatile time: " + volatileTime + "ms");
System.out.println("Synchronized time: " + synchronizedTime + "ms");
}
}
5. 指令重排序与内存屏障
指令重排序
public class InstructionReordering {
private int a = 0;
private int b = 0;
private int x = 0;
private int y = 0;
// 线程1
public void thread1() {
a = 1; // 操作1
x = b; // 操作2
// 编译器或处理器可能重排序为:x = b; a = 1;
}
// 线程2
public void thread2() {
b = 1; // 操作3
y = a; // 操作4
// 编译器或处理器可能重排序为:y = a; b = 1;
}
// 重排序可能导致的问题
public void reorderingProblem() {
// 如果重排序发生,可能出现:
// x = 0, y = 0 的情况
// 这在顺序执行中是不可能的
}
}
内存屏障类型
public class MemoryBarrierTypes {
private int x = 0;
private int y = 0;
private volatile boolean flag = false;
// 1. LoadLoad屏障
public void loadLoadBarrier() {
int temp1 = x; // Load1
// LoadLoad屏障:确保Load1在Load2之前完成
int temp2 = y; // Load2
}
// 2. StoreStore屏障
public void storeStoreBarrier() {
x = 1; // Store1
// StoreStore屏障:确保Store1在Store2之前完成
y = 2; // Store2
}
// 3. LoadStore屏障
public void loadStoreBarrier() {
int temp = x; // Load
// LoadStore屏障:确保Load在Store之前完成
y = 1; // Store
}
// 4. StoreLoad屏障
public void storeLoadBarrier() {
x = 1; // Store
// StoreLoad屏障:确保Store在Load之前完成
int temp = y; // Load
}
// volatile的内存屏障
public void volatileBarriers() {
x = 1; // 普通写
y = 2; // 普通写
// StoreStore屏障
flag = true; // volatile写
// StoreLoad屏障
if (flag) { // volatile读
// LoadLoad屏障
// LoadStore屏障
int temp = x; // 普通读
}
}
}
重排序规则
public class ReorderingRules {
private int a = 0;
private int b = 0;
private volatile int volatileVar = 0;
// 规则1:as-if-serial语义
public void asIfSerialRule() {
int temp1 = a; // 操作1
int temp2 = b; // 操作2
int result = temp1 + temp2; // 操作3
// 操作1和操作2可以重排序,但操作3不能与操作1、2重排序
// 因为操作3依赖于操作1和操作2的结果
}
// 规则2:happens-before规则
public void happensBeforeRule() {
a = 1; // 操作1
volatileVar = 1; // 操作2 (volatile写)
// 操作1不能与操作2重排序
if (volatileVar == 1) { // 操作3 (volatile读)
int temp = a; // 操作4
// 操作3不能与操作4重排序
}
}
// 规则3:数据依赖规则
public void dataDependencyRule() {
int temp = a; // 操作1
int result = temp + 1; // 操作2
// 操作2依赖于操作1的结果,不能重排序
}
}
6. final字段的线程安全语义
final字段的基本语义
public class FinalFieldSemantics {
private final int finalField;
private int normalField;
public FinalFieldSemantics(int value) {
this.normalField = value;
this.finalField = value; // final字段必须在构造函数中初始化
}
// final字段的线程安全保证
public void finalFieldSafety() {
// 1. final字段在构造函数完成后对其他线程可见
// 2. final字段不会被重排序到构造函数之外
// 3. 引用类型的final字段,其引用的对象内容可能不是线程安全的
}
}
final字段的重排序规则
public class FinalFieldReordering {
private final int finalField;
private int normalField;
public FinalFieldReordering(int value) {
this.normalField = value; // 操作1
this.finalField = value; // 操作2
// 操作1和操作2可以重排序
// 但finalField的写入不能重排序到构造函数之外
}
// 读取final字段
public void readFinalField() {
int temp = finalField; // 能读取到正确的值
int temp2 = normalField; // 可能读取到0(默认值)
}
}
final引用字段
public class FinalReferenceField {
private final int[] finalArray;
private final List<String> finalList;
public FinalReferenceField() {
this.finalArray = new int[10];
this.finalList = new ArrayList<>();
// final引用字段的引用是线程安全的
// 但引用指向的对象内容可能不是线程安全的
}
public void modifyFinalReference() {
// 不能重新赋值final引用
// this.finalArray = new int[20]; // 编译错误
// 但可以修改引用指向的对象内容
this.finalArray[0] = 1; // 需要额外的同步机制保证线程安全
this.finalList.add("item"); // 需要额外的同步机制保证线程安全
}
}
final字段的初始化安全性
public class FinalFieldInitializationSafety {
private final int finalField;
private int normalField;
public FinalFieldInitializationSafety(int value) {
this.normalField = value;
this.finalField = value;
}
// 静态final字段
private static final int STATIC_FINAL = 42;
// 静态final字段的初始化
static {
// 静态final字段在类初始化时设置
// 对后续的所有线程都可见
}
// 读取final字段
public int getFinalField() {
return finalField; // 总是能读取到正确的值
}
public int getNormalField() {
return normalField; // 可能读取到0(默认值)
}
}
实践练习
练习1:内存可见性测试
public class VisibilityTest {
private boolean flag = false;
private int count = 0;
public void testVisibility() throws InterruptedException {
// 线程1:修改共享变量
Thread writer = new Thread(() -> {
count = 42;
flag = true;
System.out.println("Writer: count=" + count + ", flag=" + flag);
});
// 线程2:读取共享变量
Thread reader = new Thread(() -> {
while (!flag) {
// 空循环,等待flag变为true
}
System.out.println("Reader: count=" + count + ", flag=" + flag);
});
reader.start();
writer.start();
writer.join();
reader.join();
}
// 使用volatile解决可见性问题
private volatile boolean volatileFlag = false;
private volatile int volatileCount = 0;
public void testVolatileVisibility() throws InterruptedException {
Thread writer = new Thread(() -> {
volatileCount = 42;
volatileFlag = true;
System.out.println("Writer: count=" + volatileCount + ", flag=" + volatileFlag);
});
Thread reader = new Thread(() -> {
while (!volatileFlag) {
// 空循环
}
System.out.println("Reader: count=" + volatileCount + ", flag=" + volatileFlag);
});
reader.start();
writer.start();
writer.join();
reader.join();
}
}
练习2:指令重排序测试
public class ReorderingTest {
private int x = 0;
private int y = 0;
private int a = 0;
private int b = 0;
public void testReordering() throws InterruptedException {
int count = 0;
int reorderCount = 0;
for (int i = 0; i < 100000; i++) {
x = 0;
y = 0;
a = 0;
b = 0;
Thread thread1 = new Thread(() -> {
a = 1;
x = b;
});
Thread thread2 = new Thread(() -> {
b = 1;
y = a;
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
count++;
if (x == 0 && y == 0) {
reorderCount++;
System.out.println("重排序发生!x=" + x + ", y=" + y);
}
}
System.out.println("总次数: " + count);
System.out.println("重排序次数: " + reorderCount);
System.out.println("重排序概率: " + (double) reorderCount / count * 100 + "%");
}
}
练习3:Happens-Before规则验证
public class HappensBeforeVerification {
private int x = 0;
private volatile boolean flag = false;
public void verifyHappensBefore() throws InterruptedException {
Thread writer = new Thread(() -> {
x = 42; // 操作1
flag = true; // 操作2 (volatile写)
});
Thread reader = new Thread(() -> {
while (!flag) {
// 等待flag变为true
}
// 操作2 happens-before 这里的操作
int temp = x; // 操作3
System.out.println("x = " + temp); // 应该输出42
});
reader.start();
writer.start();
writer.join();
reader.join();
}
}
总结
本部分深入介绍了Java内存模型与可见性的核心概念:
- JMM结构:理解主内存与工作内存的关系
- Happens-Before规则:掌握操作间的可见性关系
- volatile关键字:了解volatile的语义与限制
- 指令重排序:理解重排序的原因与影响
- 内存屏障:掌握内存屏障的作用
- final字段:了解final字段的线程安全语义
这些知识是理解并发编程底层原理的关键,下一部分将学习无锁编程与原子操作。

被折叠的 条评论
为什么被折叠?



