小姐姐都能看懂的Happens-before规则,你还愣住了

简介: 众所周知的并发编程三大特性;原子性、可见性、有序性;但是这些特性的起源你知道吗?可见性:正是由于CPU存在缓存,导致了变量修改的不可见性

前言

众所周知的并发编程三大特性;原子性、可见性、有序性;但是这些特性的起源你知道吗?

可见性:正是由于CPU存在缓存,导致了变量修改的不可见性;

原子性:线程切换是基于CPU指令而不是高级语言中的一行代码,线程切换可以发生在任意一条CPU指令执行之前,完成之后,因此只能保证CPU指令的原子性;

有序性:最诡异的特性,一行代码被拆分成多个CPU指令,但是为了保持高性能,编译器对其做了排序,可能导致顺序改变。

好了,以上的三大特性已经了解了,但是如何解决这些问题呢?

正题来了,Java内存模型提供了一种规则:happens-before;

happens-before:前面一个操作的结果对后续操作是可见的。

happens-before是Java内存模型中最晦涩的内容,其中涉及到的规则有如下六项,下文将会逐一介绍。


1. 程序的顺序性规则

这条规则指在一个线程中,按照程序顺序,程序前面对某一个变量的修改一定对后续操作可见的。

如下一段代码:

@Test
public void test(){
  int a=10;
  int b=100;
  a=11;
  b+=10;
}

上述代码中的a=11一定是对b+=10这行代码可见的。


2. volatile 变量规则

这条规则是指对一个线程对volatile 变量的写操作, Happens-Before 于后续一个线程对这个volatile 变量的读操作。

简单的理解:一个线程修改了volatile变量,则对另外一个线程读取volatile的变量是可见的。


3. 传递性

很好理解的一个特性,比如A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。

举个栗子:有如下代码:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

两个线程分别执行writer()和reader()方法,如下图:

传递性示例图


从上图可以知道以下内容:

  1. x=42对于写变量v=true是可见的,符合规则一。
  2. 写变量v=true对于读变量v==true是可见的,这是规则二的内容。

结合这个传递性,则x=42对于读变量v==true是可见的。则如果线程B读到了v==true,那么线程A设置的x=42对于线程B来说是可见的。

这个传递性的意义重大,1.5版本的并发工具包就是靠volatile搞定可见性的。


4. 管程中锁的规则

这条规则是指对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。

管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized是 Java 里对管程的实现。

管程中的锁是隐式实现的,在进入同步块之前加锁,在代码块执行完之前自动释放锁,一切都是无感知的,如下代码:

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=0
  if (this.x < 20) {
    this.x = 20; 
  }  
} //此处自动解锁

规则4是什么意思呢?假设A,B两个线程,A线程进入代码块加锁,此时的x<20,则修改x=20,执行完成后自动释放锁(解锁),此时B线程进入代码块,自动加锁,则A线程修改的x=20此时对于B线程是可见的。如下图:

管程中锁示例图



5. 线程 start() 规则

这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。

简单的理解则是如果线程A执行了线程B的start()方法,则线程A在start()之前的操作结果对于线程B都是可见的,如下代码:

Thread B = new Thread(()->{
  // 主线程调用B.start()之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,a==77
});
// 此处对共享变量a修改
a = 77;
// 启动子线程
B.start();

上述代码中,主线程在start()之前对于共享变量a的修改对于B线程是可见的。


6. 线程 join() 规则

这条是关于线程等待的。它是指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。

换句话说,如果线程A调用了线程B的join()方法,那么当线程B执行完成后,其中的所有操作对于线程A都是可见的,如下代码:

Thread B = new Thread(()->{
  // 此处对共享变量a修改
  a = 1;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,a==1

从上述代码可以知道,线程B执行完成之后,线程B对于共享变量a的修改对于主线程来说是可见的。


总结

本文着重介绍了Happens-Before的六大规则,其在Java内存模型中的作用不可小觑,你看懂了吗?

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
12天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23475 11
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
16天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
5236 19
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
17天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
6253 15
|
6天前
|
人工智能 缓存 Shell
Claude Code 全攻略:命令大全 + 实战工作流(完整版)
Claude Code 是一款运行在终端环境下的 AI 编码助手,能够直接在项目目录中理解代码结构、编辑文件、执行命令、执行开发计划,并支持持久化记忆、上下文压缩、后台任务、多模型切换等专业能力。对于日常开发、项目维护、快速重构、代码审查等场景,它可以大幅减少手动操作、提升编码效率。本文从常用命令、界面模式、核心指令、记忆机制、图片处理、进阶工作流等维度完整说明,帮助开发者快速上手并稳定使用。
1307 2
|
5天前
|
前端开发 API 内存技术
对比claude code等编程cli工具与deepseek v4的适配情况
DeepSeek V4发布后,多家编程工具因未适配其强制要求的`reasoning_content`字段而报错。本文对比Claude Code、GitHub Copilot、Langcli、OpenCode及DeepSeek-TUI等主流工具的兼容性:Claude Code需按官方方式配置;Langcli表现最佳,开箱即用且无报错;Copilot与OpenCode暂未修复问题;DeepSeek-TUI尚处早期阶段。
950 2
对比claude code等编程cli工具与deepseek v4的适配情况
|
1月前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
26209 65
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)