首页 > 其他分享 >happens-before 原则

happens-before 原则

时间:2023-07-02 15:55:46浏览次数:42  
标签:happens 原则 线程 操作 执行 public before

happens-before 简述

从 JDK 5 开始,Java 使用新的 JSR-133 内存模型。JSR-133 使用 happens-before 的概念来阐述操作之间的内存可见性。在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。《JSR-133:Java Memory Model and Thread Specification》对 happens-before 关系的定义如下。

  • 第一点,如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
  • 第二点,两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before 关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM 允许这种重排序)。

以上两点定义看上去是不是会有点矛盾?如果 A happens-before B,第一点说A的操作顺序必须排在B之前。第二点说两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before 关系来执行的结果一致,是可以重排序的,也就是说先执行A再执行B与先执行B再执行A的结果一致(要符合as-if-serial语义),B可以在A之前执行。那岂不是违反了第一条的第一个操作的执行顺序排在第二个操作之前?
这里举个例子

double pi = 3.14; // A
double r = 1.0; // B 
double area = pi * r * r; // C

根据happens-before原则,以上代码存在

A happens-before B
B happens-before C
A happens-before C (根据happens-before的传递性推导出来的,之后会讲述)

这里 A happens-before B,但实际上执行时B可以排在A之前执行,因为A和B之间并没有依赖关系,重排序的执行结果并不会对程序的执行造成影响(符合as-if-serial语义)。这是不是造成了以上两个定义的矛盾?其实并不矛盾,以上的第一点是JMM对程序员的承诺从程序员的角度来说,可以这样理解 happens-before 关系:如果 A happens-before B,那么 Java 内存模型将向程序员保证——A操作的结果将对 B 可见,且 A 的执行顺序排在 B 之前。注意,这只是 Java 内存模型向程序员做出的保证!实际上JMM 其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序正确同步的多线程程序),编译器和处理器怎么优化都行。JMM 这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。

happens-before 规则

《JSR-133:Java Memory Model and Thread Specification》定义了如下 happens-before 规则。

  1. 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
    如同上面的A happens-before B,B happens-before C
  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
public class Test {
    public int i = 0;

    public synchronized void writer(){  //1
        i++;                            //2
    }                                   //3

    public synchronized int reader(){   //4
        return i;                       //5
    }                                   //6

}
/**
 * 假设线程 A 执行 writer()方法,随后线程 B 执行 reader()方法。根据 happens-before规则,这个过程包含的 happens-before 关系可以分为 3 类。
 * 根据程序次序规则,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens-before 6。
 * 根据监视器锁规则,3 happens-before 4。
 * 根据 happens-before 的传递性,2 happens-before 5。
 */
  1. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个volatile 域的读。
class VolatileExample {
    int a = 0;
    volatile boolean flag = false;
    public void writer() {
        a = 1; // 1
        flag = true; // 2
    }
    public void reader() {
        if (flag) { // 3
            int i = a; // 4
        }
    }
}
//假设线程 A 执行 writer()方法之后,线程 B 执行 reader()方法。根据 happens-before规则,这个过程建立的 happens-before 关系可以分为 3 类:
//根据程序次序规则,1 happens-before 2;3 happens-before 4。
//根据 volatile 规则,2 happens-before 3。
//根据 happens-before 的传递性规则,1 happens-before 4。
  1. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
  2. start()规则:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的ThreadB.start()操作 happens-before 于线程 B 中的任意操作。
public class ThreadExample {
    public static int i = 0;

    public static void threadBStart(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                int j = i; //4
            }
        },"B").start(); //3
    }

    public static void main(String[] args) {
        i++; // 1
        threadBStart(); //2
    }
}
//main线程开始,这个过程建立的 happens-before 关系可以分为 3 类:
//根据程序次序规则,1 happens-before 2; 3 happens-before 4
//根据start()规则,2 happens-before 3; 2 happens-before 4;
//根据 happens-before 的传递性规则,1 happens-before 4。
  1. join()规则:如果线程 A 执行操作 ThreadB.join()并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。
public class JoinExample {
    public static int i = 0;

    public static Thread threadBStart(){
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                i ++;
            }
        }, "B");
        threadB.start();
        return threadB;
    }

    public static void main(String[] args) throws InterruptedException {
        threadBStart().join();
        int a = i;
    }
}

//线程b的所有操作happens-before main线程 join方法返回后

参考

《Java并发编程的艺术》

标签:happens,原则,线程,操作,执行,public,before
From: https://www.cnblogs.com/yuyiming/p/17520669.html

相关文章

  • 面向对象的高级原则
    1、开放/封闭原则模块应该对扩展开放,对修改关闭。每个类型应该是固定的,不在未来有任何变化,更不要修改类型的源代码。即类型对修改关闭。每次发生变化时,要通过添加新代码来增强现有类的行为,而不是修改原有代码。可以使用如下两种方式:①用组合创建新的类型。②使用安全干净的继......
  • rtos中,线程设计原则
    设计原则:运行时间长的线程的优先级应较低;一个线程完成一个功能;可重入函数:可重入函数,可被中断,在这个函数执行的任何时刻可以中断它,转入执行另一段代码,返回时,不会出现什么错误;多任务系统下,中断可能在任务执行的任何时间发生,一个函数的执行期间被中断后,到重新恢复到断点进行执......
  • mysql优化原则
    1.尽量不要在列上运算,这样会导致索引失效例如:select*fromadminwhereyear(admin_time)>2014优化为:select*fromadminwhereadmin_time>'2014-01-01′2.limit的基数比较大时,使用betweenand代替例如:select*fromadminorderbyadmin_idlimit100000,10优化为:se......
  • BGP的选路原则
    在学习BGP的选路原则前,我们需要先了解下BGP的属性BGP属性公认属性:所有BGP路由器都必须识别用支持的属性公认必遵属性:BGP的Update消息中必须包含的属性公认任意属性:不必存在于BGP的update消息中,可以根据需求自由选择的属性可选属性:不要求所有BGP路由器都能识别的属性可选过渡属性:BG......
  • 巧用AJAX的BEFORESEND 提高用户体验,避免重复数据
    https://www.cnblogs.com/lshbk/p/10930679.htmljQuery是经常使用的一个开源js框架,其中的$.ajax请求中有一个beforeSend方法,用于在向服务器发送请求前执行一些动作。具体可参考jQuery官方文档:http://api.jquery.com/Ajax_Events/$.ajax({beforeSend:function(){//H......
  • 微服务CAP原则
    微服务CAP原则 CAP原则又称CAP定理,指的是在一个分布式系统中,存在Consistency(一致性)、Availability(可用性)、Partitiontolerance(分区容错性),三者不可同时保证,最多只能保证其中的两者。 一致性(C):在分布式系统中的所有数据备份,在同一时刻都是同样的值(所有的节点无论何时访问都能......
  • [QML]从零开始QML开发(二)QML开发,浅谈控件、槽函数、锚等基本概念。QML和C++怎么交互?贯
    [QML]从零开始QML开发(二)QML开发,浅谈控件、槽函数、锚等基本概念。QML和C++怎么交互?贯彻落实MVC原则先看代码:importQtQuick2.12importQtQuick.Window2.12importQtQuick.Controls2.5Window{visible:truewidth:320height:480title:qsTr("HelloW......
  • mysql索引及索引创建原则
    1.mysql索引及索引创建原则目录1.mysql索引及索引创建原则1.1.使用场景1.1.1.什么时候用索引1.1.2.索引的弱点1.1.3.MySQL会使用到索引的场景如下:1.2.查看表上的索引1.3.索引类型1.3.1.组合索引1.3.1.1.组合索引生效规则1.3.2.前缀索引1.3.3.函数索引1.3.4.唯一索......
  • 设计模式的原则(一)
    相信自己,无论自己到了什么局面,请一定要继续相信自己。新的世界开始了,接下来,老蝴蝶带领大家学习一下设计模式。我们先了解一下设计原则一.设计模式一.一设计原则设计模式常用的七大原则:单一职责原则接口隔离原则依赖倒转(倒置)原则里氏替换原则开闭原则迪米特法则合成复用原则一.......
  • Cannot Reference “XxxClass.xxxmember” Before Supertype Constructor Has Been Ca
    在调用超类型构造函数之前无法引用“XxxClass.xxx”-----在一个类的构造器方法还未执行的时候,我们无法使用这个类的成员属性或成员方法。百度翻译:在调用超类型构造函数之前无法引用“XxxClass.xxx”-----我的理解:在一个类的构造器方法还未执行的时候,我们......