JMM内存模型之happens-before阐述
文章目录
- 一、happens-before的定义
- 二、happens-before的规则
- 1. 程序顺序规则:
- 2. 监视器锁规则:
- 3. volatile变量规则:
- 4. 传递性:
- 5. start()规则:
- 6. join()规则:
一、happens-before的定义
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照 happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法
二、happens-before的规则
1. 程序顺序规则:
一个线程中的每个操作,happens-before于该线程中的任意后续操作。
程序顺序规则(Program Order Rule)指的是在单个线程内,按照程序的顺序,前面的操作 happens-before 后面的操作。这意味着一个线程中的每个操作都会在该线程中的任意后续操作之前发生。
下面是一个简单的Java程序示例,展示了程序顺序规则的应用:
public class ProgramOrderDemo {private int count = 0;public void increment() {count++; // 操作1}public void printCount() {System.out.println("Count: " + count); // 操作2}public static void main(String[] args) {ProgramOrderDemo demo = new ProgramOrderDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 线程1中的操作1demo.printCount(); // 线程1中的操作2});Thread thread2 = new Thread(() -> {demo.increment(); // 线程2中的操作1demo.printCount(); // 线程2中的操作2});thread1.start();thread2.start();}
}
在上述示例中,我们创建了两个线程:thread1和thread2,它们都会调用ProgramOrderDemo类中的increment()和printCount()方法。
根据程序顺序规则,线程1中的操作1(count++)happens-before 线程1中的操作2(System.out.println("Count: " + count)),同样线程2中的操作1(count++)happens-before 线程2中的操作2(System.out.println("Count: " + count))。
这意味着在每个线程中,count++操作一定会在System.out.println("Count: " + count)操作之前发生。因此,我们可以确保在每个线程中打印的count值是递增的。
请注意,尽管两个线程并行执行,但由于程序顺序规则的存在,各个线程内部的操作顺序是有序的,不会出现线程间的竞争条件。
2. 监视器锁规则:
对一个锁的解锁,happens-before于随后对这个锁的加锁。
监视器锁规则(Monitor Lock Rule)指的是对一个锁的解锁操作 happens-before 于随后对同一个锁的加锁操作。这个规则确保了在多线程环境下,对共享资源的同步访问。
下面是一个简单的Java程序示例,展示了监视器锁规则的应用:
public class MonitorLockDemo {private int count = 0;private Object lock = new Object();public void increment() {synchronized (lock) {count++; // 线程1中的操作1}}public void printCount() {synchronized (lock) {System.out.println("Count: " + count); // 线程2中的操作2}}public static void main(String[] args) {MonitorLockDemo demo = new MonitorLockDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 线程1中的操作1});Thread thread2 = new Thread(() -> {demo.printCount(); // 线程2中的操作2});thread1.start();thread2.start();}
}
在上述示例中,我们创建了两个线程:thread1和thread2,它们都会调用MonitorLockDemo类中的increment()和printCount()方法。在这个类中,我们使用了一个对象lock作为锁。
根据监视器锁规则,线程1中的操作1(count++)happens-before 线程2中的操作2(System.out.println("Count: " + count))。
这意味着在线程1中,对锁的解锁操作一定会在线程2中对同一个锁的加锁操作之前发生。因此,我们可以确保在执行打印操作时,count的值是线程1已经更新过的。
请注意,通过使用synchronized关键字和共享的锁对象,我们确保了对count的访问是同步的,避免了竞态条件和数据不一致的问题。这是因为监视器锁规则确保了解锁操作 happens-before 加锁操作,从而确保了对共享资源的正确同步访问。
3. volatile变量规则:
对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
volatile变量规则(Volatile Variable Rule)指的是对一个volatile域的写操作 happens-before 于任意后续对这个volatile域的读操作。这个规则确保了在多线程环境下,对volatile变量的可见性和顺序性。
下面是一个简单的Java程序示例,展示了volatile变量规则的应用:
public class VolatileVariableDemo {private volatile int number = 0;public void writeNumber() {number = 42; // 写操作}public void readNumber() {System.out.println("Number: " + number); // 读操作}public static void main(String[] args) {VolatileVariableDemo demo = new VolatileVariableDemo();Thread thread1 = new Thread(() -> {demo.writeNumber(); // 写操作});Thread thread2 = new Thread(() -> {demo.readNumber(); // 读操作});thread1.start();thread2.start();}
}
在上述示例中,我们创建了两个线程:thread1和thread2,它们都会调用VolatileVariableDemo类中的writeNumber()和readNumber()方法。在这个类中,我们使用了一个volatile修饰的变量number。
根据volatile变量规则,线程1中的写操作(number = 42)happens-before 线程2中的读操作(System.out.println("Number: " + number))。
这意味着在线程1中,对number的写操作一定会在线程2中对同一个number的读操作之前发生。因此,我们可以确保在执行打印操作时,读取到的number的值是线程1已经写入的。
请注意,使用volatile修饰变量可以保证其在多线程环境下的可见性和顺序性。这意味着对volatile变量的写操作对其他线程是可见的,并且读操作一定会读取到最新的值。这是因为volatile变量规则确保了对volatile变量的写 happens-before 于任意后续对这个volatile变量的读。
4. 传递性:
如果A happens-before B,且B happens-before C,那么A happens-before C。
下面是一个新的示例,展示了happens-before关系的传递性:
public class TransitivityDemo {private int number = 0;private volatile boolean ready = false;public void writer() {number = 42; // 写操作ready = true; // 写操作}public void reader() {if (ready) { // 读操作System.out.println("Number: " + number); // 读操作}}public static void main(String[] args) {TransitivityDemo demo = new TransitivityDemo();Thread thread1 = new Thread(() -> {demo.writer(); // 写操作});Thread thread2 = new Thread(() -> {demo.reader(); // 读操作});thread1.start();thread2.start();}
}
在这个示例中,我们有一个TransitivityDemo类,其中包含一个number变量和一个ready变量。在writer()方法中,我们首先进行写操作number = 42,然后进行写操作ready = true。在reader()方法中,我们进行读操作if (ready),如果ready为true,则进行读操作System.out.println("Number: " + number)。
根据happens-before关系的传递性,线程1中的写操作number = 42 happens-before 线程1中的写操作ready = true。同时,线程1中的写操作ready = true happens-before 线程2中的读操作if (ready)。因此,我们可以得出结论,线程1中的写操作number = 42 happens-before 线程2中的读操作if (ready)。
由于happens-before关系的传递性,我们可以得出A happens-before C的结论,即线程1中的写操作number = 42 happens-before 线程2中的读操作System.out.println("Number: " + number)。
这个示例演示了happens-before关系的传递性,其中A happens-before B,B happens-before C,因此A happens-before C。
5. start()规则:
如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的 ThreadB.start()操作happens-before于线程B中的任意操作。
当线程A执行ThreadB.start()方法启动线程B时,根据Java内存模型(Java Memory Model,JMM)中的start()规则,线程A的ThreadB.start()操作将happens-before于线程B中的任意操作。这意味着线程B中的任意操作都可以看到线程A在调用ThreadB.start()之前的操作。
下面是一个简单的示例代码来展示这个规则:
public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");ready = true;threadB.start(); // 线程A启动线程B});threadA.start(); // 启动线程AthreadA.join(); // 等待线程A执行完毕System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");// 在这里可以看到线程A在调用ThreadB.start()之前的操作System.out.println("Thread B sees ready? " + ready);}}
}
在这个示例中,线程A首先会执行一些工作,并将ready属性设置为true。然后,线程A调用threadB.start()来启动线程B。在线程B的run()方法中,我们可以看到线程A在调用ThreadB.start()之前的操作,即输出Thread B sees ready? true。
因此,根据start()规则,线程A的ThreadB.start()操作happens-before于线程B中的任意操作,确保了对ready属性的修改对线程B可见。
运行结果如下:

6. join()规则:
如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作 happens-before于线程A从ThreadB.join()操作成功返回。
根据Java内存模型(Java Memory Model,JMM)中的join()规则,如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。这意味着线程A能够看到线程B在join()之前的操作。
下面是一个简单的示例代码来展示这个规则:
public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");threadB.start(); // 线程A启动线程Btry {threadB.join(); // 线程A等待线程B执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread A sees ready? " + ready);});threadA.start(); // 启动线程AthreadA.join(); // 等待线程A执行完毕System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");ready = true; // 在这里修改ready属性}}
}
在这个示例中,线程A首先会执行一些工作,并启动线程B。然后,线程A调用threadB.join()来等待线程B执行完毕。在线程B的run()方法中,我们将ready属性设置为true。
当线程A从threadB.join()成功返回后,它能够看到线程B在join()之前的操作。因此,线程A输出的Thread A sees ready? true将显示线程B在join()之前将ready属性设置为true。
因此,根据join()规则,线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回,确保了对ready属性的修改对线程A可见。
运行结果:

相关文章:
JMM内存模型之happens-before阐述
文章目录 一、happens-before的定义二、happens-before的规则1. 程序顺序规则:2. 监视器锁规则:3. volatile变量规则:4. 传递性:5. start()规则:6. join()规则: 一、happens-before的定义 如果一个操作hap…...
大数据课程I2——Kafka的架构
文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Kafka的架构; ⚪ 掌握Kafka的Topic与Partition; 一、Kafka核心概念及操作 1. producer生产者,可以是一个测试线程,也可以是某种技术框架(比如flume)。 2. producer向kafka生…...
vscode如何汉化
首先我们到vscode官网下载 链接如下: Visual Studio Code - Code Editing. Redefined 根据自己需要的版本下载就好 下载并且安装完毕之后 运行vscode 然后按快捷键 CTRLSHIFTX 打开安装扩展界面 搜索简体中文 安装就可以了 谢谢大家观看...
matlab保存图片
仅作为记录,大佬请跳过。 文章目录 用界面中的“另存为”用saveas 用界面中的“另存为” 即可。 参考 感谢大佬博主文章:传送门 用saveas 必须在编辑器中的plot之后用saveas(也就是不能在命令行中单独使用——比如在编辑器中plot…...
产业园区数字孪生3d可视化全景展示方案
随着数字经济的发展,数字技术给企业发展带来了机遇的同时,也为企业管理带来挑战。比如园区运维,不仅体量大,复杂的运维管理系统,落地难度也较高。那么如何通过数字化手段重塑园区运营,打通园区各业务数据孤…...
centos7 jupyter notebook 安装自动补全插件
激活juoyter notebook的安装环境 conda activate prod执行以下命令安装 pip install jupyter_contrib_nbextensions -i https://pypi.tuna.tsinghua.edu.cn/simple jupyter contrib nbextension install --userpip install jupyter_nbextensions_configurator -i https://py…...
【算法——双指针】LeetCode 202 快乐数
题目描述: 思路:快慢指针 看到循环,我就想起了快慢指针的方法,从题目我们可以看出,我们需要模拟一个过程:不断用当前的数去生成下一个数,生成的规则就是将当前数的各位的平方累加; …...
AndroidManifest清单文件中,Activity的screenOrientation属性详解
screenOrientation用于控制Acivity的屏幕方向,参数有16个。 参数值功能自动旋转打开自动旋转关闭unspecified-1让系统决定Activity的方向,由传感器和系统设置共同决定四个方向不旋转landscape0强制为横屏,忽略传感器和系统设置不旋转不旋转portrait1强制为竖屏,忽略传感器和系统…...
Qt+Pyhton实现麒麟V10系统下word文档读写功能
目录 前言1.C调用python1.1 安装Python开发环境1.2 修改Qt工程配置1.3 初始化Python环境1.4 C 调用Python 函数1.5 常用的Python接口 2.python虚拟环境2.1Python虚拟环境简介2.2 virtualenv 安装及使用2.3 在C程序中配置virtualenv 虚拟环境 3.python-docx库的应用4.总结 前言 …...
TCP/IP 下的计算机网络江湖
〇、引言 在当今数字化时代,计算机网络宛如广袤江湖,涵盖着五大门派:物理层、数据链路层、网络层、传输层和应用层。每个门派独具技能,共同构筑着现代网络的框架。物理层宛如江湖基石,将比特流传输;数据链路层如武林传承,组织数据帧传递;网络层则像导航大师,寻找传送路…...
智能家居(4)---火灾报警线程封装
封装火灾报警线程实现智能家居中的火灾报警功能 mainPro.c(主函数) #include <stdio.h> #include "controlDevice.h" #include "inputCommand.h"#include <pthread.h>struct Devices *pdeviceHead NULL; …...
C#语音播报问题之 无法嵌入互操作类型SpVoiceClass,请改用适用的窗口
C#语音播报问题之 无法嵌入互操作类型SpVoiceClass,请改用适用的窗口 解决办法如下: 只需要将引入的Interop.SpeechLib的属性嵌入互操作类型改为false 改为false 即可解决!...
C语言实例_获取文件MD5值
一、MD5介绍 MD5(Message Digest Algorithm 5)是一种常用的哈希函数算法。将任意长度的数据作为输入,并生成一个唯一的、固定长度(通常是128位)的哈希值,称为MD5值。MD5算法以其高度可靠性和广泛应用而闻名…...
Win11环境下 Unity个人版无法激活
网上教程大多都是在win10环境下运行,win11环境下遇到很多没有碰到的问题,故简单做个记录,也方便同样使用win11的朋友解决问题。 Unity2021无法打开 问题描述:下载Unity2021.3.4f1c1版本(LTS)后࿰…...
C++:模拟实现list及迭代器类模板优化方法
文章目录 迭代器模拟实现 本篇模拟实现简单的list和一些其他注意的点 迭代器 如下所示是利用拷贝构造将一个链表中的数据挪动到另外一个链表中,构造两个相同的链表 list(const list<T>& lt) {emptyinit();for (auto e : lt){push_back(e);} }void test_…...
k8s整合istio配置gateway入口、配置集群内部服务调用管理
一、 istio gateway使用demo kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata:name: ngdemo-gatewaynamespace: ssx spec:selector:istio: ingressgateway # use Istio default gateway implementationservers:- port:numbe…...
工程监测振弦采集仪采集到的数据如何进行分析和处理
工程监测振弦采集仪采集到的数据如何进行分析和处理 振弦采集仪是一个用于测量和记录物体振动的设备。它通过测量物体表面的振动来提取振动信号数据,然后将其转换为数字信号,以便进行分析和处理。在实际应用中,振弦采集仪是广泛应用于机械、建…...
(三)行为模式:2、命令模式(Command Pattern)(C++示例)
目录 1、命令模式(Command Pattern)含义 2、命令模式的UML图学习 3、命令模式的应用场景 4、命令模式的优缺点 5、C实现命令模式的实例 1、命令模式(Command Pattern)含义 命令模式(Command)ÿ…...
微信小程序 蓝牙设备连接,控制开关灯
1.前言 微信小程序中连接蓝牙设备,信息写入流程 1、检测当前使用设备(如自己的手机)是否支持蓝牙/蓝牙开启状态 wx:openBluetoothAdapter({}) 2、如蓝牙已开启状态,检查蓝牙适配器的状态 wx.getBluetoothAdapterState({}) 3、添加…...
Python 矢量数据库和矢量索引:构建 LLM 应用程序
推荐:使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 由于使用其硬件创建的生成式AI应用程序,Nvidia经历了显着的增长。另一项软件创新,矢量数据库,也正在乘着生成式人工智能的浪潮。 开发人员正在向量数据库上用Pytho…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
【深尚想】TPS54618CQRTERQ1汽车级同步降压转换器电源芯片全面解析
1. 元器件定义与技术特点 TPS54618CQRTERQ1 是德州仪器(TI)推出的一款 汽车级同步降压转换器(DC-DC开关稳压器),属于高性能电源管理芯片。核心特性包括: 输入电压范围:2.95V–6V,输…...
Appium下载安装配置保姆教程(图文详解)
目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...
