📜 了解 JMM(Java 内存模型)中的 Happens-Before 规则 🌟
在 Java 并发编程中,JMM(Java Memory Model)是非常重要的概念,它定义了线程如何与内存交互,以及如何确保线程之间的通信一致性。JMM 引入了 Happens-Before 规则 来约束操作之间的顺序关系,保证数据的可见性和一致性。
现在我们一起学习 JMM 中定义的几条主要的 Happens-Before 规则吧!
1️⃣ 什么是 Happens-Before 规则?
Happens-Before 是一种用于定义内存访问顺序的规则。
- 如果操作 A “Happens-Before” 操作 B,那么:
- 操作 A 的结果对操作 B 可见(数据正确同步)。
- 操作 A 必须在操作 B 之前完成(时间和因果顺序)。
JMM 中的 Happens-Before 告诉我们哪些操作是线程安全、不会引发并发问题的。
2️⃣ JMM 中定义的主要 Happens-Before 规则
以下是 JMM 中几条重要的 Happens-Before 规则:
(1)程序顺序规则 📋
- 同一个线程内,代码的语句按程序的书写顺序执行。
- 即在线程中,语句
A
按照在代码中的位置发生,随后才是语句B
。
例如:
int a = 10; // 操作 A
int b = a * 2; // 操作 B
这里,a = 10
Happens-Before b = a * 2
,因为它写在前面。
(2)监视器锁规则 🔒
- 锁的释放 Happens-Before 锁的获取。
- 一个线程释放锁后,另一个线程获取该锁,另一个线程可以看到前一个线程对共享数据的修改。
例如:
synchronized (lock) {
x = 100; // 主线程操作 A,释放锁
}
synchronized (lock) {
System.out.println(x); // 另一个线程操作 B,获取锁
}
在这里,锁的释放 (操作 A
) Happens-Before 另一个线程获取锁 (操作 B
),因此,线程 B 将看到 x = 100
。
(3)启动线程规则 🚀
- 主线程调用
Thread.start()
Happens-Before 新线程的所有操作。 - 这就是说,主线程中对变量的写操作在调用
Thread.start()
时会被新线程看到。
例如:
Thread t = new Thread(() -> System.out.println(sharedVar));
sharedVar = 42;
t.start(); // 主线程启动 t,这里 Happens-Before 新线程的打印逻辑
这里的 t.start()
Happens-Before 新线程中对 sharedVar
的访问。
(4)线程终止规则 🛑
- 线程的终止 Happens-Before 检测到线程结束的操作(
Thread.join()
或Thread.isAlive()
)。 - 如果主线程等待某个子线程完成(通过调用
join()
),那么子线程完成后的所有操作对主线程可见。
例如:
Thread t = new Thread(() -> sharedVar = 42);
t.start();
t.join(); // 等待子线程结束
System.out.println(sharedVar); // 子线程终止后,这里能保证看到 sharedVar = 42
在这里,t.join()
Happens-Before 主线程中的打印语句。
(5)中断规则 🌟
- 对线程的
interrupt()
方法调用 Happens-Before 检测到该线程的中断事件(通过isInterrupted()
或捕获InterruptedException
)。 - 即中断信号发送比被检测中断发生得早。
(6)Volatile 规则 ⚡
- 对
volatile
变量的写操作 Happens-Before 读操作。 - 如果一个线程对
volatile
变量进行了写操作,另一个线程读取该变量会看到最新的值。
例如:
volatile int sharedVar = 0;
Thread t1 = new Thread(() -> sharedVar = 42); // 写入操作
Thread t2 = new Thread(() -> System.out.println(sharedVar)); // 读取操作
在这里,sharedVar
的写操作 Happens-Before sharedVar
的读操作,因此线程 t2
能看到线程 t1
修改后的值。