解读Java内存模型中Happens-Before的8个原则
摘要:本文我们就结合案例程序来说明Java内存模型中的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();
}
}
运行结果如下所示。
构造方法
对象销毁
解读Java内存模型中Happens-Before的8个原则的更多相关文章
- 深入理解Java内存模型中的虚拟机栈
深入理解Java内存模型中的虚拟机栈 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都会有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启 ...
- (第三章)Java内存模型(中)
一.volatile的内存语义 1.1 volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面通过具体 ...
- 什么是Java内存模型中的happens-before
Java内存模型JMM Java内存模型(即Java Memory Model , 简称JMM),本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序个各个变量(包括实 ...
- Java内存模型中volatile关键字的作用
volatile作用总结: 1. 强制线程从公共内存中取得变量的值,而不是从线程的私有的本地内存中,volatile修饰的变量不具有原子性(修改一个变量的值不能同步). 2. 保证volatile修饰 ...
- 从JVM设计角度解读Java内存模型
第十六章:Java内存模型 本文我们将重点放在Java内存模型(JMM)的一些高层设计问题,以及JMM的底层需求和所提供的保证,还有一些高层设计原则背后的原理. 例如安全发布,同步策略的规范以及一致性 ...
- java内存模型中工作内存并不一定会同步主内存的情况分析
其实是为了填之前的一个坑 在一个多线程的案例中出现了阻塞的情况. https://www.cnblogs.com/hetutu-5238/p/10477875.html 其中的第二个问题,即多个 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...
- 全面理解Java内存模型(JMM)
理解Java内存区域与Java内存模型Java内存区域 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁的时机,其中蓝色部分代表的是所有线程共享的数据区 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
随机推荐
- OpenResty入门之压测篇:压测工具界的 “悍马” wrk 审核中
在上篇文章 每个后端都应该了解的 OpenResty 入门以及网关安全实战 中,我向大家介绍了 OpenResty 的入门使用是 WAF 防御实战,这篇文章将给大家继续介绍 OpenResty 入门之 ...
- 2023寒鹭Tron-CTF迎新赛 CRYPTO Misc 全WP
CRYPTO 简简单单 1.题目信息 U2FsdGVkX1+2gTXPuTetdM1p+IETUDXAHe2eC33jQfgdJoOmmrJq 2.解题方法 兔子密码,在线工具直接解 简简单单2 1. ...
- CSS 元素居中方式总结
作者:WangMin 格言:努力做好自己喜欢的每一件事 在开发过程中,很多网页需求要求我们居中一个div,比如html文档流当中的一块div,比如弹出层内容部分这种脱离了文档流等.不同的情况有不同的居 ...
- 邮差之死--python源代码
"""sth imported""" import time import os '''2 flags''' flag = 0 tmp = ...
- TIOBE 11月榜单:Java和 C# 之间的差距缩小到0.7
TIOBE 公布了 2023 年 11 月的编程语言排行榜. 虽然这期重点介绍的是Kotlin,本月,它的排名上升了 0.17%,从第 18 位上升到第 15 位,前进了 3 位. TIOBE的10月 ...
- Ubuntu环境下C++使用onnxruntime和Opencv进行YOLOv8模型部署
目录 环境配置 系统环境 项目文件路径 文件环境 config.txt CMakeLists.txt type.names 读取config.txt配置文件 修改图片尺寸格式 读取缺陷标志文件 生成缺 ...
- MIGO配置过账后自动打印
1.业务需求 本文档介绍,当MIGO发货过账时,自动打印自开发表格. 2.打印程序 复制标准配置中调用的程序和子例程,到新的程序(路径可参考下文系统配置) 只用复制子例程entry_wa01即可,参考 ...
- 自学day7 数组
typora-copy-images-to: media 数组 一.概念 对象中可以通过键值对存储多个数据,且数据的类型是没有限制的,所以通常会存储一个商品的信息或一个人的信息: var obj = ...
- 一道入门的java安全题
[XCTF]Zhuanxv 收获 java题的一般流程 HQL注入 SQL注入 看题 目录扫描 dirsearch扫目录,发现list目录: 一个登录界面,本着尽量不写sql注入题目的原则(因为太菜了 ...
- C++ Qt开发:字符串QString容器
在Qt框架中,QString 是一个强大而灵活的字符串容器,专为处理 Unicode 字符而设计.它提供了许多方便的方法来操作和处理字符串,使得在跨平台开发中能够轻松地进行文本操作.QString 是 ...