当前位置: 首页 > 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…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...