解读Java内存模型中Happens-Before的8个原则

解读,java,内存,模型,happens,before,原则 · 浏览次数 : 71

小编点评

## 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。 **原则四:锁定规则** 对一个锁的解锁操作Happens-Before于后续对这个锁的加锁操作。例如,下面的代码,在进入synchronized代码块之前,会自动加锁,在代码块执行完毕后,会自动释放锁。 **原则五:线程启动规则** 线程启动规则如果线程A调用线程B的start()方法来启动线程B,则start()操作Happens-Before于线程B中的任意操作。 **原则六:线程终结规则** 线程A等待线程B完成(在线程A中调用线程B的join()方法实现),当线程B完成后(线程A调用线程B的join()方法返回),则线程A能够访问到线程B对共享变量的操作。 **原则七:线程中断规则** 线程中断规则对线程interrupt()方法的调用Happens-Before于被中断线程的代码检测到中断事件的发生。 **原则八:对象终结原则** 对象终结原则一个对象的初始化完成Happens-Before于它的finalize()方法的开始。

正文

摘要:本文我们就结合案例程序来说明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内存模型中Happens-Before的8个原则

摘要:本文我们就结合案例程序来说明Java内存模型中的Happens-Before原则。 本文分享自华为云社区《【高并发】一文秒懂Happens-Before原则》,作者: 冰 河。 在正式介绍Happens-Before原则之前,我们先来看一段代码。 【示例一】 class VolatileExa

[转帖]Redis进阶(发布订阅,PipeLine,持久化,内存淘汰)

目录 1、发布订阅 1.1 什么是发布订阅 1.2 客户端实例演示 1.3 Java API演示 1.4 Redis发布订阅和rabbitmq的区别 2、批量操作 2.1 普通模式与 PipeLine 模式 2.2 适用场景 2.3 源码解析 2.4 Pipelining的局限性 2.5 事务与 L

一文了解JVM面试篇(上)

Java内存区域 1、如何解释 Java 堆空间及 GC? 当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建 堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一 个进程,回收无效对象的内存用于将来的分配。 2、JVM 的主要组成部分

深入探讨Java面试中内存泄漏:如何识别、预防和解决

引言 在编写和维护Java应用程序时,内存泄漏是一个重要的问题,可能导致性能下降和不稳定性。本文将介绍内存泄漏的概念,为什么它在Java应用程序中如此重要,并明确本文的目标,即识别、预防和解决内存泄漏问题。 内存泄漏的概念 内存泄漏是指应用程序中分配的内存(通常是堆内存)在不再需要时未能正确释放。这

【后端面经-Java】JVM内存分区详解

本文主要介绍了JVM内存分区的基本情况,着重介绍了栈、堆、方法区的分区情况,并给出实际代码解释内存分配的过程。

比 poi导入导出更好用的 EasyExcel使用小结

转载请注明出处: 官方文档: https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read 1.简洁 Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,p

[转帖]线上Java 高CPU占用、高内存占用排查思路

一、前言 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题。当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警。本文主要针对系统运行缓慢这一问题,提供该问题的排查思路,从而定位出问题的代码点,进而提供解决该问题的思路。 二、分析

[转帖]jvm一般相关配置OutOfMemoryError关参数配置解释

一般运行java应用都会根据实际情况设置一些jvm相关运行参数 特别是有关内存和oom溢出等参数,方便后续问题定位和解决 如常用的以下配置 nohup java -Xms256m -Xmx24g -Xmn8g -verbose:gc -XX:+PrintGCDateStamps -XX:+Print

[转帖]Java实战之OutOfMemoryError异常问题及解决方法

https://www.jb51.net/article/244872.htm + 目录 在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError (下文称OOM)异常的可能。本篇主要结合着【深入理解Java虚拟机】一书当中整理了本篇博客

[转帖]高手总结的9种 OOM 常见原因及解决方案

https://zhuanlan.zhihu.com/p/79355050 当 JVM 内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误。本文总结了常见的 OOM 原因及其解决方法,如下图所示。如有遗漏或错误,欢迎补充指正。 1、Java heap space 当