摘要:在并发编程中,Happens-Before原则是我们必须要掌握的,今天我们就一起来详细聊聊并发编程中的Happens-Before原则。

本文分享自华为云社区《【高并发】一文秒懂Happens-Before原则》,作者:冰 河。

在并发编程中,Happens-Before原则是我们必须要掌握的,今天我们就一起来详细聊聊并发编程中的Happens-Before原则。

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

【示例一】

class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
} public void reader() {
if (v == true) {
//x的值是多少呢?
}
}
}

以上示例来源于:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong

这里,假设线程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代码块之前,会自动加锁,在代码块执行完毕后,会自动释放锁。

【示例二】

public class Test{
private int x = 0;
public void initX{
synchronized(this){ //自动加锁
if(this.x < 10){
this.x = 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之前的操作。

我们来看下面的代码。

【示例三】

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

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

【原则六】线程终结规则

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

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

【示例四】

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

【原则七】线程中断规则

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

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

【示例五】

 //在线程A中将x变量的值初始化为0
private int x = 0; public void execute(){
//在线程A中初始化线程B
Thread threadB = new Thread(()->{
//线程B检测自己是否被中断
if (Thread.currentThread().isInterrupted()){
//如果线程B被中断,则此时X的值为100
System.out.println(x);
}
});
//在线程A中启动线程B
threadB.start();
//在线程A中将共享变量X的值修改为100
x = 100;
//在线程A中中断线程B
threadB.interrupt();
}

【原则八】对象终结原则

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

例如,下面的程序代码。

【示例六】

public class TestThread {

   public TestThread(){
System.out.println("构造方法");
} @Override
protected void finalize() throws Throwable {
System.out.println("对象销毁");
} public static void main(String[] args){
new TestThread();
System.gc();
}
}

运行结果如下所示。

构造方法
对象销毁

好了,今天就到这儿吧。我们下期见~~

点击关注,第一时间了解华为云新鲜技术~

8大原则带你秒懂Happens-Before原则的更多相关文章

  1. Asp.Net上传大文件带进度条swfupload

    Asp.Net基于swfupload上传大文件带进度条百分比显示,漂亮大气上档次,大文件无压力,先看效果 一.上传效果图 1.上传前界面:图片不喜欢可以自己换 2.上传中界面:百分比显示 3.上传后返 ...

  2. 【转】JS大总结(带实例)

    JS大总结(带实例) JavaScript事务查询综合click() 对象.click() 使对象被点击.closed 对象.closed 对象窗口是否已封闭true/falseclearTimeou ...

  3. 设计模式之迪米特原则(LOD)(最少知识原则)

    来源:迪米特法则(LoD)最初是用来作为面向对象的系统设计风格的一种法则,是很多著名系统,如火星登陆软件系统.木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则. 迪米特法则(LoD)又可分为两种:狭义 ...

  4. SOLDI原则之DIP:依赖倒置原则

    本篇介绍软件设计原则之一DIP:依赖倒置原则.很多知识回头来看会有新的理解.看到一句话,一段文字,一个观点有了新的理解,醍醐灌顶的感觉.这种感觉像是一种惊喜.古语说:温故而知新. DIP:依赖倒置原则 ...

  5. Java大神带你领略queue的风采

    作为数据结构中比较常见的类型,你足够了解队列(queue)吗?从今天开始,我将为你讲解关于队列(queue)的一切,包括概念.类型和具体使用方法,如果你对此足够感兴趣,赶快来加入我们,我将同你一起探索 ...

  6. 一文带你秒懂商业智能BI的价值所在!

    首先,先来跟大家解释说明一下什么是商业智能?这应该也是刚刚接触商业智能的小伙伴最想要了解的问题,平时刷微博.看头条时都会看到数据可视化.数据分析和大数据等词汇时,但是对它们如同熟悉的陌生人一般,那么这 ...

  7. 老猪带你玩转自定义控件三——sai大神带我实现ios 8 时间滚轮控件

    ios 8 的时间滚轮控件实现了扁平化,带来很好用户体验,android没有现成控件,小弟不才,数学与算法知识不过关,顾十分苦恼,幸好在github上找到sai大神实现代码,甚为欣喜,顾把学习这个控件 ...

  8. 【附案例】UI交互设计不会做?设计大神带你开启动效灵感之路

    随着网络技术的创新发展,如今UI交互设计应用越来越广泛,显然已经成为设计的主流及流行的必然趋势.UI界面交互设计中的动效包括移动,滑块,悬停效果,GIF动画等.UI界面交互设计为何越来越受到青睐?它有 ...

  9. 大神带你一天了解zabbix(一)

    第15章 Zabbix的搭建 15.1 为什么使用监控服务 对系统实现不间断的监控,实现报警通知(电话,微信,邮件,发短信,手环) 实时反馈系统当前的状态信息 保证服务的可靠安全性 保证业务的稳定运行 ...

随机推荐

  1. Arcscene教程

    ​ ​ ​ ​ ​ ​ ​ ​ ​ ​​ ​ 筛选​ ​ ​ ​ ​ ​ ​ ​ 看不清的话可以进行如下操作:右键-->属性-->符号系统-->把高程前面的对号取消-->添加- ...

  2. 题解 CF241E Flights

    题目传送门 题目大意 给出一个 \(n\) 个点 \(m\) 条边的 \(\texttt{DAG}\) ,给每条边设定边权为 \(1\) 或者 \(2\) ,使得 \(1\to n\) 的每条路径长度 ...

  3. RPAaaS是什么?为何能够推进RPA人人可用?

    RPAaaS是什么?为何能够推进RPA人人可用? 助力中小企业快速实现自动化,RPAaaS加速"RPA人人可用"时代到来 相对传统RPA拥有更多优势,PRAaaS为RPA行业带来更 ...

  4. JavaScript常用的Hook脚本

    JavaScript常用的Hook脚本 本文Hook脚本 来自 包子 页面最早加载代码Hook时机 在source里 用dom事件断点的script断点 然后刷新网页,就会断在第一个js标签,这时候就 ...

  5. SQL Server 数据库单用户模式处理

    在还原数据库bak备份文件时,由于某种原因(具体何种原因在此不进行分析)导致数据库还原后处于单用户模式,如下图: 单个用户模式导致,数据库无法打开,只能通过脚本去查询数据库内的表,然后进行查询数据,极 ...

  6. Rvalue References

    Rvalue References

  7. 在kivy中加图片

    from kivy.app import App from kivy.uix.scatterlayout import ScatterLayout from kivy.uix.image import ...

  8. UML图 | 时序图(顺序、序列图)绘制

    上一次写过一篇 UML | 类图 相关的文章,平时规范开发会用的上,或者是写什么文档,就还是需要画图,就像毕业设计就是如此.希望能够帮助到大家. 注:本文中所用画图软件为 Microsoft Visi ...

  9. [no_code][Beta]事后分析

    设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们要解决的目前的手写表单的电子化问题,办公电子化问题的一个key问题.定义十分清楚: 输入: 手写表单 ...

  10. USART 硬件流控

    流控的概念源于 RS232 这个标准,在 RS232 标准里面包含了串口.流控的定义.大家一定了解,RS232 中的"RS"是Recommend Standard 的缩写,即&qu ...