当前位置: 首页 > ops >正文

happen-before原则

什么是 happen-before 原则?

happen-before 是一个逻辑关系,用于描述两个操作之间的 “先后顺序”—— 如果操作 A happen-before 操作 B,那么 A 的执行结果必须对 B 可见,且 A 的执行顺序在逻辑上先于 B。也就是保证指令有序性和共享变量的可见性。

具体的 happen-before 规则

JMM 定义了 8 条核心 happen-before 规则,每条规则都直接或间接关联可见性:

规则名称具体描述可见性体现举例
程序次序规则单线程内,按代码顺序,前面的操作 happen-before 后面的操作。线程内先给 a=1,再打印 a,一定能读到 1(单线程可见性天然成立)。
管程锁定规则解锁操作 happen-before 后续对同一把锁的加锁操作。线程 A 解锁前修改 x=2,线程 B 加锁后一定能读到 x=2(锁保证可见性)。 例如:synchronized 关键字、java.util.concurrent.locks.Lock 接口的实现类包括ReentrantLock、ReentrantReadWriteLock
volatile 变量规则volatile 变量的写操作 happen-before 后续对该变量的读操作。线程 A 写 volatile x=3,线程 B 读 x 一定能得到 3volatile 保证可见性)。
线程启动规则Thread.start() 操作 happen-before 线程内的任意操作。主线程启动子线程前设置 flag=true,子线程启动后一定能读到 flag=true
线程终止规则线程内的任意操作 happen-before 其他线程检测到该线程终止(如 join())。子线程修改 count=5,主线程通过 join() 等待子线程结束后,一定能读到 count=5
线程中断规则对线程的中断操作 happen-before 被中断线程检测到中断事件(如 isInterrupted())。线程 A 中断线程 B,线程 B 后续调用 isInterrupted() 一定能感知到中断。
传递性规则若 A happen-before B,且 B happen-before C,则 A happen-before C。若 A 写 volatile x,B 读 x 后写 y,C 读 y 则能感知 A 的修改(传递可见)。
final 字段规则对象初始化完成(构造函数返回)happen-before 对其 final 字段的访问。对象构造时给 final x=10,其他线程访问 x 一定能得到 10final 可见性)。

synchronized 关键字

最基础的内置锁,通过同步代码块或同步方法实现:
进入 synchronized 块(加锁)时,线程会清空本地缓存,从主内存加载共享变量的最新值。
退出 synchronized 块(解锁)时,线程会将本地缓存中修改的共享变量刷新到主内存。
示例:

private int count = 0;// 同步方法
public synchronized void increment() {count++; // 解锁时会将修改刷新到主内存
}// 同步代码块
public void getCount() {synchronized (this) {return count; // 加锁时会从主内存加载最新值}
}

java.util.concurrent.locks.Lock 接口的实现类

显式锁,最常用的实现是 ReentrantLock,还包括 ReentrantReadWriteLock 等:
调用 lock() 方法(加锁)时,线程会失效本地缓存,强制从主内存加载变量。
调用 unlock() 方法(解锁)时,线程会将本地缓存中的修改刷新到主内存。
示例(ReentrantLock):

private final Lock lock = new ReentrantLock();
private int count = 0;public void increment() {lock.lock();try {count++; // 解锁时刷新到主内存} finally {lock.unlock();}
}public int getCount() {lock.lock();try {return count; // 加锁时从主内存加载} finally {lock.unlock();}
}

读写锁 ReentrantReadWriteLock
分离读锁和写锁,更细粒度的控制:
写锁(writeLock()):获取时会强制加载最新值,释放时会刷新修改到主内存(同普通锁)。
读锁(readLock()):多个线程可同时获取,能看到之前写锁释放的所有修改(保证读操作可见性)。

著名的双重检查单例模式

public class Singleton {// 关键1:使用volatile修饰单例实例private static volatile Singleton instance;// 关键2:私有构造函数,防止外部直接实例化private Singleton() {// 初始化逻辑}// 关键3:双重检查锁定获取实例public static Singleton getInstance() {// 第一次检查:避免不必要的同步(提高性能)if (instance == null) {// 关键4:同步块,保证多线程安全synchronized (Singleton.class) {// 第二次检查:防止多线程同时进入同步块后重复创建实例if (instance == null) {// 关键5:创建实例(volatile在此处防止指令重排序)instance = new Singleton();}}}return instance;}
}

关键代码解析

volatile 修饰符的作用
volatile 在这里有两个核心作用:
保证 instance 变量的可见性(多线程环境下,一个线程对 instance 的修改会立即被其他线程感知),因为第一次检查并使用synchronized 关键字将instance 包含在内,所以必须使用volatile关键字保证可见性。
禁止指令重排序(这是 DCL 模式中 volatile 的核心价值)。
双重检查的意义
第一次检查(同步块外):避免每次调用 getInstance() 都进入同步块,提高性能(多数情况下 instance 已初始化,无需同步)。
第二次检查(同步块内):防止多个线程同时通过第一次检查后,在同步块内重复创建实例。

volatile 如何禁止指令重排序?

对象创建过程(instance = new Singleton())在 JVM 中会被拆分为三步操作:

1. memory = allocate();       // 分配内存空间
2. ctorInstance(memory);      // 初始化对象(执行构造函数)
3. instance = memory;         // 将引用指向内存地址

问题场景:

如果没有 volatile 修饰,编译器或 CPU 可能对步骤 2 和 3 进行重排序,导致执行顺序变为:1 → 3 → 2。
此时会出现严重问题:

线程 A 执行到步骤 3 后,instance 已非 null(引用已指向内存),但步骤 2 尚未完成(对象未初始化)。
线程 B 此时进行第一次检查(instance == null),会发现 instance 不为 null,直接返回一个未初始化完成的对象,导致程序异常。

volatile 的解决方案:

volatile 通过在对象创建指令前后插入内存屏障(Memory Barrier) 禁止这种重排序:

在步骤 3 之后插入 StoreStore 屏障:禁止初始化对象(步骤 2)与设置引用(步骤 3)的重排序。
在步骤 3 之后插入 StoreLoad 屏障:确保引用赋值(步骤 3)完成后,才允许其他线程读取 instance。

这两个内存屏障强制保证了执行顺序为 1 → 2 → 3,即对象完全初始化后,才会将引用赋值给 instance,从而避免线程 B 读取到未初始化的对象。

http://www.xdnf.cn/news/20236.html

相关文章:

  • WSL Ubuntu Docker 代理自动配置教程
  • LeetCode 139. 单词拆分 - 动态规划解法详解
  • 【软考架构】第二章 计算机系统基础知识:计算机网络
  • 主数据系统是否对于企业是必需的?
  • 最大似然估计:损失函数的底层数学原理
  • 基本数据类型和包装类的区别?
  • 2025年大数据专业人士认证发展路径分析
  • MySQL运维补充
  • 【目录-判断】鸿蒙HarmonyOS开发者基础
  • 敏捷scrum管理实战经验总结
  • 贪心算法应用:化工反应器调度问题详解
  • 【LLIE专题】SIED:看穿0.0001lux的极致黑暗
  • NPU边缘推理识物系统
  • 懒加载的概念
  • 新能源风口正劲,“充电第一股”能链智电为何掉队?
  • 操作系统启动过程详解
  • Coze源码分析-资源库-删除插件-前端源码-核心组件实现
  • 03-生产问题-慢SQL-20250926
  • 机器人控制器开发(导航算法——导航栈关联坐标系)
  • 创客匠人:什么是“好的创始人IP”
  • 2025年本体论:公理与规则的挑战与趋势
  • CentOS系统停服,系统迁移Ubuntu LTS
  • 【CSS,DaisyUI】自定义选取内容的颜色主题
  • Android开发——初步了解AndroidManifest.xml
  • 零基础入门深度学习:从理论到实战,GitHub+开源资源全指南(2025最新版)
  • C++ 条件变量 通知 cv.notify_all() 先释放锁再通知
  • [光学原理与应用-428]:非线性光学 - 为什么要改变光的波长/频率,获得特点波长/频率的光?
  • RocketMQ如何处理消息堆积
  • 云某惠旧案再审可能性与商业创新实践:积分运营的边界与实体商家机遇
  • 【设计模式】 工厂方法模式