• Happens-Before原则


    目录

    【原则一】程序次序规则

    【原则二】volatile变量规则

    【原则三】传递规则

    【原则四】监视器锁规则

    【原则五】线程启动规则

    【原则六】线程终结规则

    【原则七】线程中断规则

    【原则八】对象终结原则


    在正式介绍Happens-Before原则之前,我们先来看一段代码。 【示例一】

    1. class VolatileExample {
    2. int x = 0;
    3. volatile boolean v = false;
    4. public void writer() {
    5. x = 42;
    6. v = true;
    7. }
    8. public void reader() {
    9. if (v == true) {
    10. //x的值是多少呢?
    11. }
    12. }
    13. }

    复制

    这里,假设线程A执行writer()方法,按照volatile会将v=true写入内存;线程B执行reader()方法,按照volatile,线程B会从内存中读取变量v,如果线程B读取到的变量v为true,那么,此时的变量x的值是多少呢??

    这个示例程序给人的直觉就是x的值为42,其实,x的值具体是多少和JDK的版本有关,如果使用的JDK版本低于1.5,则x的值可能为42,也可能为0。如果使用1.5及1.5以上版本的JDK,则x的值就是42。

    看到这个,就会有人提出问题了?这是为什么呢?其实,答案就是在JDK1.5版本中的Java内存模型中引入了Happens-Before原则。

    接下来,我们就结合案例程序来说明Java内存模型中的Happens-Before原则。

    【原则一】程序次序规则

    在一个线程中,按照代码的顺序,前面的操作Happens-Before于后面的任意操作。

    例如【示例一】中的程序x=42会在v=true之前执行。这个规则比较符合单线程的思维:在同一个线程中,程序在前面对某个变量的修改一定是对后续操作可见的。

    【原则二】volatile变量规则

    对一个volatile变量的写操作,Happens-Before于后续对这个变量的读操作。

    也就是说,对一个使用了volatile变量的写操作,先行发生于后面对这个变量的读操作。这个需要大家重点理解。

    【原则三】传递规则

    如果A Happens-Before B,并且B Happens-Before C,则A Happens-Before C。

    我们结合【原则一】、【原则二】和【原则三】再来看【示例一】程序,此时,我们可以得出如下结论:

    (1)x = 42 Happens-Before 写变量v = true,符合【原则一】程序次序规则。

    (2)写变量v = true Happens-Before 读变量v = true,符合【原则二】volatile变量规则。

    再根据【原则三】传递规则,我们可以得出结论:x = 42 Happens-Before 读变量v=true。

    也就是说,如果线程B读取到了v=true,那么,线程A设置的x = 42对线程B就是可见的。换句话说,就是此时的线程B能够访问到x=42。

    其实,Java 1.5版本的 java.util.concurrent并发工具就是靠volatile语义来实现可见性的。

    【原则四】监视器锁规

    对一个锁的解锁操作 Happens-Before于后续对这个锁的加锁操作。

    例如,下面的代码,在进入synchronized代码块之前,会自动加锁,在代码块执行完毕后,会自动释放锁。

    【示例二】

    1. public class Test{
    2. private int x = 0;
    3. public void initX{
    4. synchronized(this){ //自动加锁
    5. if(this.x < 10){
    6. this.x = 10;
    7. }
    8. } //自动释放锁
    9. }
    10. }

    复制

    我们可以这样理解这段程序:假设变量x的值为10,线程A执行完synchronized代码块之后将x变量的值修改为10,并释放synchronized锁。当线程B进入synchronized代码块时,能够获取到线程A对x变量的写操作,也就是说,线程B访问到的x变量的值为10。

    【原则五】线程启动规则

    如果线程A调用线程B的start()方法来启动线程B,则start()操作Happens-Before于线程B中的任意操作。

    我们也可以这样理解线程启动规则:线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作。

    我们来看下面的代码。

    【示例三】

    1. //在线程A中初始化线程B
    2. Thread threadB = new Thread(()->{
    3. //此处的变量x的值是多少呢?答案是100
    4. });
    5. //线程A在启动线程B之前将共享变量x的值修改为100
    6. x = 100;
    7. //启动线程B
    8. threadB.start();

    复制

    上述代码是在线程A中执行的一个代码片段,根据【原则五】线程的启动规则,线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作,在线程B中访问到的x变量的值为100。

    【原则六】线程终结规则

    线程A等待线程B完成(在线程A中调用线程B的join()方法实现),当线程B完成后(线程A调用线程B的join()方法返回),则线程A能够访问到线程B对共享变量的操作。

    例如,在线程A中进行的如下操作。

    【示例四】

    1. Thread threadB = new Thread(()-{
    2. //在线程B中,将共享变量x的值修改为100
    3. x = 100;
    4. });
    5. //在线程A中启动线程B
    6. threadB.start();
    7. //在线程A中等待线程B执行完成
    8. threadB.join();
    9. //此处访问共享变量x的值为100

    复制

    【原则七】线程中断规则

    对线程interrupt()方法的调用Happens-Before于被中断线程的代码检测到中断事件的发生。

    例如,下面的程序代码。在线程A中中断线程B之前,将共享变量x的值修改为100,则当线程B检测到中断事件时,访问到的x变量的值为100。

    【示例五】

    1. //在线程A中将x变量的值初始化为0
    2. private int x = 0;
    3. public void execute(){
    4. //在线程A中初始化线程B
    5. Thread threadB = new Thread(()->{
    6. //线程B检测自己是否被中断
    7. if (Thread.currentThread().isInterrupted()){
    8. //如果线程B被中断,则此时X的值为100
    9. System.out.println(x);
    10. }
    11. });
    12. //在线程A中启动线程B
    13. threadB.start();
    14. //在线程A中将共享变量X的值修改为100
    15. x = 100;
    16. //在线程A中中断线程B
    17. threadB.interrupt();
    18. }

    复制

    【原则八】对象终结原则

    一个对象的初始化完成Happens-Before于它的finalize()方法的开始。

    例如,下面的程序代码。

    【示例六】

    1. public class TestThread {
    2. public TestThread(){
    3. System.out.println("构造方法");
    4. }
    5. @Override
    6. protected void finalize() throws Throwable {
    7. System.out.println("对象销毁");
    8. }
    9. public static void main(String[] args){
    10. new TestThread();
    11. System.gc();
    12. }
    13. }

    复制

    运行结果如下所示。

    1. 构造方法
    2. 对象销毁
  • 相关阅读:
    npm/nodejs安装、切换源
    野火霸天虎 STM32F407 学习笔记_5 按键输入;位带操作介绍
    vue父子组件传值:父传子、子传父
    使用mfuzz进行时间序列转录组数据聚类,划分相似表达模式基因群
    Java-KoTime:接口耗时监测与邮件通知接口耗时情况
    Alad de Qnget
    他做国外LEAD,用了一年时间,把所有房贷都还清了
    【AI视野·今日Robot 机器人论文速览 第三十六期】Tue, 19 Sep 2023
    Day08
    Vue复习笔记 (三)mixin
  • 原文地址:https://blog.csdn.net/Sword52888/article/details/126079993