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

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可见。

运行结果如下:
happens-before

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可见。

运行结果:
happens-before

相关文章:

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&#xf…...

产业园区数字孪生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&#xff08;主函数&#xff09; #include <stdio.h> #include "controlDevice.h" #include "inputCommand.h"#include <pthread.h>struct Devices *pdeviceHead NULL; …...

C#语音播报问题之 无法嵌入互操作类型SpVoiceClass,请改用适用的窗口

C#语音播报问题之 无法嵌入互操作类型SpVoiceClass&#xff0c;请改用适用的窗口 解决办法如下&#xff1a; 只需要将引入的Interop.SpeechLib的属性嵌入互操作类型改为false 改为false 即可解决&#xff01;...

C语言实例_获取文件MD5值

一、MD5介绍 MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常用的哈希函数算法。将任意长度的数据作为输入&#xff0c;并生成一个唯一的、固定长度&#xff08;通常是128位&#xff09;的哈希值&#xff0c;称为MD5值。MD5算法以其高度可靠性和广泛应用而闻名…...

Win11环境下 Unity个人版无法激活

网上教程大多都是在win10环境下运行&#xff0c;win11环境下遇到很多没有碰到的问题&#xff0c;故简单做个记录&#xff0c;也方便同样使用win11的朋友解决问题。 Unity2021无法打开 问题描述&#xff1a;下载Unity2021.3.4f1c1版本&#xff08;LTS&#xff09;后&#xff0…...

C++:模拟实现list及迭代器类模板优化方法

文章目录 迭代器模拟实现 本篇模拟实现简单的list和一些其他注意的点 迭代器 如下所示是利用拷贝构造将一个链表中的数据挪动到另外一个链表中&#xff0c;构造两个相同的链表 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…...

工程监测振弦采集仪采集到的数据如何进行分析和处理

工程监测振弦采集仪采集到的数据如何进行分析和处理 振弦采集仪是一个用于测量和记录物体振动的设备。它通过测量物体表面的振动来提取振动信号数据&#xff0c;然后将其转换为数字信号&#xff0c;以便进行分析和处理。在实际应用中&#xff0c;振弦采集仪是广泛应用于机械、建…...

(三)行为模式:2、命令模式(Command Pattern)(C++示例)

目录 1、命令模式&#xff08;Command Pattern&#xff09;含义 2、命令模式的UML图学习 3、命令模式的应用场景 4、命令模式的优缺点 5、C实现命令模式的实例 1、命令模式&#xff08;Command Pattern&#xff09;含义 命令模式&#xff08;Command&#xff09;&#xff…...

微信小程序 蓝牙设备连接,控制开关灯

1.前言 微信小程序中连接蓝牙设备&#xff0c;信息写入流程 1、检测当前使用设备&#xff08;如自己的手机&#xff09;是否支持蓝牙/蓝牙开启状态 wx:openBluetoothAdapter({}) 2、如蓝牙已开启状态&#xff0c;检查蓝牙适配器的状态 wx.getBluetoothAdapterState({}) 3、添加…...

Python 矢量数据库和矢量索引:构建 LLM 应用程序

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 由于使用其硬件创建的生成式AI应用程序&#xff0c;Nvidia经历了显着的增长。另一项软件创新&#xff0c;矢量数据库&#xff0c;也正在乘着生成式人工智能的浪潮。 开发人员正在向量数据库上用Pytho…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...

PH热榜 | 2025-06-08

1. Thiings 标语&#xff1a;一套超过1900个免费AI生成的3D图标集合 介绍&#xff1a;Thiings是一个不断扩展的免费AI生成3D图标库&#xff0c;目前已有超过1900个图标。你可以按照主题浏览&#xff0c;生成自己的图标&#xff0c;或者下载整个图标集。所有图标都可以在个人或…...

CTF show 数学不及格

拿到题目先查一下壳&#xff0c;看一下信息 发现是一个ELF文件&#xff0c;64位的 ​ 用IDA Pro 64 打开这个文件 ​ 然后点击F5进行伪代码转换 可以看到有五个if判断&#xff0c;第一个argc ! 5这个判断并没有起太大作用&#xff0c;主要是下面四个if判断 ​ 根据题目…...