JMM内存模型之happens-before阐述

news2024/11/24 17:18:59

文章目录

  • 一、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中的操作1
            demo.printCount(); // 线程1中的操作2
        });

        Thread thread2 = new Thread(() -> {
            demo.increment(); // 线程2中的操作1
            demo.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(); // 启动线程A
        threadA.join(); // 等待线程A执行完毕
        System.out.println("Thread B is ready? " + threadB.isReady());
    }

    static class ThreadB extends Thread {
        public boolean isReady() {
            return ready;
        }

        @Override
        public 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启动线程B
            try {
                threadB.join(); // 线程A等待线程B执行完毕
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread A sees ready? " + ready);
        });

        threadA.start(); // 启动线程A
        threadA.join(); // 等待线程A执行完毕
        System.out.println("Thread B is ready? " + threadB.isReady());
    }

    static class ThreadB extends Thread {
        public boolean isReady() {
            return ready;
        }

        @Override
        public 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/891877.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++坦克大战源代码

源码: #include <iostream> #include <time.h> #include <windows.h>#define W 1 //上 #define S 2 //下 #define A 3 //左 #define D 4 //右 #define L 5 // 坦克有4条命void HideCursor() { //隐藏光标 …

[LeetCode]两数相加

解题 思路1 : 先将每个链表的节点数求出来,将短的链表的每个节点的值加到长链表对应的节点上,在判断加上之后的值是否大于10,若大于则该节点减10,下一个节点加1,由此循环之后,只有长链表的最后一个节点的数可能大于10,则对最后一个节点进行判断,如果大于10,则new一个新的节点,将…

videojs 播放视频

背景&#xff1a;在项目中使用第三方插件videojs进行播放视频&#xff0c;点击事件更改播放的数据源。 一、视频相关理论 (一)、背景 网络流媒体的呈现形式分为两种&#xff1a;直播点播 (二)、流媒体的3种协议 分类&#xff1a;HTTPHLSRTMP定义&#xff1a;基于HTTP的流媒体…

踩坑串口通信 serialPort.RtsEnable = true

背景&#xff1a; 最近在调试一个激光模块&#xff0c;使用的是422通信&#xff0c;然后买了一个485转422的转换器。 通过串口监控软件观察&#xff0c;明明和串口助手发的东西一模一样&#xff0c;但是就是不返回&#xff01; 解决方案&#xff1a; 我加了&#xff0c;这句&…

2023年最新国内八款低代码平台盘点,国内低代码是否已经跑出独角兽?

什么是低代码平台&#xff1f;低代码平台的优势在哪里&#xff1f;低代码平台是否已经形成了自己的核心竞争力&#xff1f;低代码平台是否在国内跑出独角兽&#xff1f;本篇&#xff0c;我们将一起针对上述问题深入浅出的分析低代码平台的特点和前景&#xff0c;并且为大家盘点…

【推荐】深入浅出学习Spring框架【中】

目录 1.AOP是什么? 2.案列&#xff1a; 3.spring的aop的专业术语 4.代码模拟 4.1 前置通知 3.2.后置通知 3.3.环绕通知 3.4.异常通知 3.5.过滤通知 1.AOP是什么? 面向切面编程&#xff08;Aspect-Oriented Programming&#xff09;是一种编程范式&#xff0c;它的主要…

Python面向对象版本贪吃蛇实现

先来一波效果图吧 看看如何设计代码实现 import random import sysimport pygame# 游戏状态 ready 未开始 gameing 游戏中 end 游戏结束class Util:"""工具类&#xff1a; 提供静态方法"""staticmethoddef click_check(sprite):""&…

Java基础篇--错误处理机制

尽管人人希望自己身体健康&#xff0c;处理的事情都能顺利进行&#xff0c;但在实际生活中总会遇到各种状况&#xff0c;比如感冒发烧&#xff0c;工作时电脑蓝屏、死机等。同样&#xff0c;在程序运行的过程中&#xff0c;也会发生各种非正常状况&#xff0c;例如&#xff0c;…

Netty注解实现服务调用

在之前完成了原生服务间的简单通信&#xff0c;现在我们将它整合到Spring环境中&#xff0c;这里就以实现服务的远程调用&#xff0c;简单模拟即可&#xff0c;具体代码需要自己动手改造。 既然是服务调用&#xff0c;那我们就使用代理模式来实现。 新建代理类&#xff0c;这里…

java八股文面试[java基础]—— 重载 和 重写

Java中&#xff0c;有一个名叫方法签名的东西&#xff0c;它的定义是这样的 Definition: Two of the components of a method declaration comprise the method signature—the method’s name and the parameter types. 大概意思是&#xff0c;方法签名有两部分组成——方法…

14、缓存预热+缓存雪崩+缓存击穿+缓存穿透

缓存预热缓存雪崩缓存击穿缓存穿透 ● 缓存预热、雪崩、穿透、击穿分别是什么&#xff1f;你遇到过那几个情况&#xff1f; ● 缓存预热你是怎么做到的&#xff1f; ● 如何避免或者减少缓存雪崩&#xff1f; ● 穿透和击穿有什么区别&#xff1f;它两一个意思还是截然不同&am…

8月16日上课内容 部署LVS-DR群集

本章结构&#xff1a; 数据包流向分析: 数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xf…

Linux 网络发包流程

哈喽大家好&#xff0c;我是咸鱼 之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的 简单回顾一下&#xff1a; 数据到达网卡之后&#xff0c;网卡通过 DMA 将数据放到内存分配好的一块 ring buffer 中&#xff0c;然后触发硬中断CPU 收到硬中…

跨境外贸业务,选择动态IP还是静态IP?

在跨境业务中&#xff0c;代理IP是一个关键工具。它们提供了匿名的盾牌&#xff0c;有助于克服网络服务器针对数据提取设置的限制。无论你是需要经营管理跨境电商店铺、社交平台广告投放&#xff0c;还是独立站SEO优化&#xff0c;代理IP都可以让你的业务程度更加丝滑&#xff…

神经网络基础-神经网络补充概念-54-softmax回归

概念 Softmax回归&#xff08;Softmax Regression&#xff09;是一种用于多分类任务的机器学习算法&#xff0c;特别是在神经网络中常用于输出层来进行分类。它是Logistic回归在多分类问题上的推广。 原理 Softmax回归的主要思想是将原始的线性分数&#xff08;得分&#xf…

【学习日记】【FreeRTOS】任务调度时如何考虑任务优先级——任务的自动切换

写在前面 本文开始为 RTOS 加入考虑任务优先级的自动调度算法&#xff0c;代码大部分参考野火。 本文主要是一篇学习笔记&#xff0c;加入了笔者自己对野火代码的梳理和理解。 一、基本思路 首先我们要知道&#xff0c;在 RTOS 中&#xff0c;优先级越高、越需要被先执行的的…

小程序商品如何指定人员

一般而言&#xff0c;商家小程序中有很多商品&#xff0c;不同商品可能由不同的供应商提供。当客户购买商品时&#xff0c;如何直接将订单发给不同的供应商呢&#xff1f;下面就来具体介绍一下。 1. 设置订单分发模式。在 订单管理->待处理订单 后面点击设置按钮&#xff0…

cve-2016-7193:wwlib 模块堆数据结构溢出

简介 漏洞编号&#xff1a;cve-2016-7193漏洞类型&#xff1a;堆溢出软件名称&#xff1a;Office模块名称&#xff1a;wwlib历史漏洞&#xff1a;较多影响的版本 攻击利用&#xff1a;APT 攻击利器-Word 漏洞 CVE-2016-7193 原理揭秘 操作环境 系统&#xff1a;Win10 1607软…

编译器过程

编译器过程 如果这个框架对应LLVM,为什么这么说LLVM是个框架呢?是因为它提供了中间表示的定义,即前端输出的文本格式定义. 那么 "前端" 可以是两者其一 : Clang 或者 LLVM-GCC "通用优化" 和 "x86后端" 是 LLVM 提供的. // LLVM 也提供 riscv后…

网络机顶盒什么牌子好?自费5000+测评整理网络机顶盒排行榜

在挑选网络机顶盒的时候很多人贪便宜选山寨杂牌&#xff0c;买回家问题频发&#xff0c;我做数码测评几年来身边的朋友们总会问我网络机顶盒什么牌子好&#xff0c;我自费购入了将近二十款网络机顶盒&#xff0c;通过软硬件的全方位对比后整理了网络机顶盒排行榜TOP5&#xff1…