广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java内存模型中的happen-before关系详解
  • 765
分享到

Java内存模型中的happen-before关系详解

JavahappenbeforeJava内存模型 2022-11-13 19:11:32 765人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言概述为什么需要 JMM,它试图解决什么问题?JMM 是怎么解决可见性等问题的呢?后记前言 Java 语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来

前言

Java 语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来了强大、灵活的多线程机制,也带来了线程安全等令人混淆的问题,而 Java 内存模型(Java Memory Model,JMM)为我们提供了一个在纷乱之中达成一致的指导准则。

本篇博文的重点是,Java 内存模型中的 happen-before 是什么?

概述

Happen-before 关系,是 Java 内存模型中保证多线程操作可见性的机制,也是对早期语言规范中含糊的可见性概念的一个精确定义。

它的具体表现形式,包括但远不止是我们直觉中的 synchronized、volatile、lock 操作顺序等方面,例如:

  • 线程内执行的每个操作,都保证 happen-before 后面的操作,这就保证了基本的程序顺序规则,这是开发者在书写程序时的基本约定。
  • 对于 volatile 变量,对它的写操作,保证 happen-before 在随后对该变量的读取操作。
  • 对于一个的解锁操作,保证 happen-before 加锁操作。
  • 对象构建完成,保证 happen-before 于 finalizer 的开始动作。
  • 甚至是类似线程内部操作的完成,保证 happen-before 其他 Thread.join() 的线程等。

这些 happen-before 关系是存在着传递性的,如果满足 a happen-before b 和 b happen-before c,那么 a happen-before c 也成立。

前面我一直用 happen-before,而不是简单说前后,是因为它不仅仅是对执行时间的保证,也包括对内存读、写操作顺序的保证。仅仅是时钟顺序上的先后,并不能保证线程交互的可见性。

为什么需要 JMM,它试图解决什么问题?

Java 是最早尝试提供内存模型的语言,这是简化多线程编程、保证程序可移植性的一个飞跃。早期类似 C、c++ 等语言,并不存在内存模型的概念(C++ 11 中也引入了标准内存模型),其行为依赖于处理器本身的内存一致性模型,但不同的处理器可能差异很大,所以一段 C++ 程序在处理器 A 上运行正常,并不能保证其在处理器 B 上也是一致的。

即使如此,最初的 Java 语言规范仍然是存在着缺陷的,当时的目标是,希望 Java 程序可以充分利用现代硬件的计算能力,同时保持“书写一次,到处执行”的能力。

但是,显然问题的复杂度被低估了,随着 Java 被运行在越来越多的平台上,人们发现,过于泛泛的内存模型定义,存在很多模棱两可之处,对 synchronized 或 volatile 等,类似指令重排序时的行为,并没有提供清晰规范。这里说的指令重排序,既可以是编译器优化行为,也可能是源自于现代处理器的乱序执行等。

换句话说:

  • 既不能保证一些多线程程序的正确性,例如最著名的就是双检锁(Double-Checked Locking,DCL)的失效问题,双检锁可能导致未完整初始化的对象被访问,理论上这叫并发编程中的安全发布(Safe Publication)失败。
  • 也不能保证同一段程序在不同的处理器架构上表现一致,例如有的处理器支持缓存一致性,有的不支持,各自都有自己的内存排序模型。

所以,Java 迫切需要一个完善的 JMM,能够让普通 Java 开发者和编译器、JVM 工程师,能够清晰地达成共识。换句话说,可以相对简单并准确地判断出,多线程程序什么样的执行序列是符合规范的。

所以:

  • 对于编译器、JVM 开发者,关注点可能是如何使用类似内存屏障(Memory-Barrier)之类技术,保证执行结果符合 JMM 的推断。
  • 对于 Java 应用开发者,则可能更加关注 volatile、synchronized 等语义,如何利用类似 happen-before 的规则,写出可靠的多线程应用,而不是利用一些“秘籍”去糊弄编译器、JVM。

我画了一个简单的角色层次图,不同工程师分工合作,其实所处的层面是有区别的。JMM 为 Java 工程师隔离了不同处理器内存排序的区别,这也是为什么我通常不建议过早深入处理器体系结构,某种意义上来说,这样本就违背了 JMM 的初衷。

JMM 是怎么解决可见性等问题的呢?

在这里有必要简要介绍一下典型的问题场景。

在 【JAVA】JVM 内存区域的划分 里介绍了 JVM 内部的运行时数据区,但是真正程序执行,实际是要跑在具体的处理器内核上。你可以简单理解为,把本地变量等数据从内存加载到缓存、寄存器,然后运算结束写回主内存。你可以从下面示意图,看这两种模型的对应。

看上去很美好,但是当多线程共享变量时,情况就复杂了。试想,如果处理器对某个共享变量进行了修改,可能只是体现在该内核的缓存里,这是个本地状态,而运行在其他内核上的线程,可能还是加载的旧状态,这很可能导致一致性的问题。从理论上来说,多线程共享引入了复杂的数据依赖性,不管编译器、处理器怎么做重排序,都必须尊重数据依赖性的要求,否则就打破了正确性!这就是 JMM 所要解决的问题。

JMM 内部的实现通常是依赖于所谓的内存屏障,通过禁止某些重排序的方式,提供内存可见性保证,也就是实现了各种 happen-before 规则。与此同时,更多复杂度在于,需要尽量确保各种编译器、各种体系结构的处理器,都能够提供一致的行为。

我以 volatile 为例,看看如何利用内存屏障实现 JMM 定义的可见性?

对于一个 volatile 变量:

  • 对该变量的写操作之后,编译器会插入一个写屏障
  • 对该变量的读操作之前,编译器会插入一个读屏障

内存屏障能够在类似变量读、写操作之后,保证其他线程对 volatile 变量的修改对当前线程可见,或者本地修改对其他线程提供可见性。换句话说,线程写入,写屏障会通过类似强迫刷出处理器缓存的方式,让其他线程能够拿到最新数值。

如果你对更多内存屏障的细节感兴趣,或者想了解不同体系结构的处理器模型,建议参考 jsR-133 相关文档,我个人认为这些都是和特定硬件相关的,内存屏障之类只是实现 JMM 规范的技术手段,并不是规范的要求。

从应用开发者的角度,JMM 提供的可见性,体现在类似 volatile 上,具体行为是什么样呢?

我这里循序渐进的举两个例子。

首先,请看下面的代码片段,希望达到的效果是,当 condition 被赋值为 false 时,线程 A 能够从循环中退出。

// Thread A
while (condition) {
}
// Thread B
condition = false;

这里就需要 condition 被定义为 volatile 变量,不然其数值变化,往往并不能被线程 A 感知,进而无法退出。当然,也可以在 while 中,添加能够直接或间接起到类似效果的代码。

第二,我想举 Brian Goetz 提供的一个经典用例,使用 volatile 作为守卫对象,实现某种程度上轻量级的同步,请看代码片段:

Map configOptions;
char[] configText;
volatile boolean initialized = false;
// Thread A
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
// Thread B
while (!initialized)
  sleep();
// use configOptions

JSR-133 重新定义的 JMM 模型,能够保证线程 B 获取的 configOptions 是更新后的数值。

也就是说 volatile 变量的可见性发生了增强,能够起到守护其上下文的作用。线程 A 对 volatile 变量的赋值,会强制将该变量自己和当时其他变量的状态都刷出缓存,为线程 B 提供可见性。当然,这也是以一定的性能开销作为代价的,但毕竟带来了更加简单的多线程行为。

我们经常会说 volatile 比 synchronized 之类更加轻量,但轻量也仅仅是相对的,volatile 的读、写仍然要比普通的读写要开销更大,所以如果你是在性能高度敏感的场景,除非你确定需要它的语义,不然慎用。

后记

以上就是 【JAVA】Java 内存模型中的 happen-before 的所有内容了;

从 happen-before 关系开始,帮你理解了什么是 Java 内存模型。为了更方便理解,我作了简化,从不同工程师的角色划分等角度,阐述了问题的由来,以及 JMM 是如何通过类似内存屏障等技术实现的。最后,我以 volatile 为例,分析了可见性在多线程场景中的典型用例。

更多关于Java happen before的资料请关注编程网其它相关文章!

--结束END--

本文标题: Java内存模型中的happen-before关系详解

本文链接: https://www.lsjlt.com/news/170470.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java内存模型中的happen-before关系详解
    目录前言概述为什么需要 JMM,它试图解决什么问题?JMM 是怎么解决可见性等问题的呢?后记前言 Java 语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来...
    99+
    2022-11-13
    Java happen before Java 内存模型
  • Java内存模型之happens-before概念详解
    简介 happens-before是JMM的核心概念。理解happens-before是了解JMM的关键。 1、设计意图 JMM的设计需要考虑两个方面,分别是程序员角度和编译器、处理...
    99+
    2022-11-12
  • 详解Java的内存模型
    目录JVM的内存模型Java “一次运行,到处编译” 的真面目JVM的本质和位置JVM的内存模型总览线程私有区域线程共享区域直接内存从例子来理解内存模型JVM的内存模型 Java “...
    99+
    2022-11-12
  • Java内存模型详解
    目录什么是JMM主存与工作内存volatile 关键字有什么用一个线程对共享变量做了修改之后,其他的线程能够看到(感知到)该变量的这种修改(变化)什么是JMM JMM全称Java M...
    99+
    2023-05-18
    Java内存模型 Java JMM模型
  • Java——内存模型详解!
    Java内存模型是一种抽象的规则或规范,定义了程序中存在竞争现象的对象(包括实例字段、静态字段和数组对象,不包括局部变量,形式参数;后者是线程私有,不存在竞争问题)的访问方式。         如果我们要想深入了解Java并发编程,就要先理...
    99+
    2023-10-20
    java 开发语言
  • Java内存模型JMM详解
    Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性、是否可以重排序等问题的无关具体平台的统一的保证。(可能在术语上与Java运行时内存分布有歧义,后者指堆、方法区、线程栈等内存...
    99+
    2023-05-30
    java 内存模型 详解
  • JAVA内存模型(JMM)详解
    目录前言JAVA并发三大特性可见性有序性原子性Java内存模型真面目Happens-Before规则1.程序的顺序性规则2. volatile 变量规则3.传递性锁的规则5.线程 s...
    99+
    2022-12-08
    JAVA 内存模型 java内存模型和jvm内存模型的区别 java jmm模型
  • Java内存区域与内存模型详解
    这篇文章主要介绍“Java内存区域与内存模型详解”,在日常操作中,相信很多人在Java内存区域与内存模型详解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java内存区域与内存模型详解”的疑惑有所帮助!接下来...
    99+
    2023-06-02
  • Java内存模型图文详解
    1. 概述多任务和高并发是衡量一台计算机处理器的能力重要指标之一。一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题,它代表着一秒内服务器平均能响应的请求数,...
    99+
    2014-08-03
    Java
  • 详解JVM系列之内存模型
    目录1. 内存模型和运行时数据区2、思维导图和图例3、对象向JVM申请空间4、为什么需要Survivor区?5、为什么需要两个Survivor区?6、例子进行验证堆内...
    99+
    2022-11-12
  • java高并发的volatile与Java内存模型详解
    public class Demo09 { public static boolean flag = true; public static class T1...
    99+
    2022-11-12
  • Java内存模型的深入讲解
    目录内存模型硬件架构Java内存模型与硬件关联对象的可见性竞争条件总结Java内存模型展示了Java虚拟机是如何与计算机内存交互的,解决多线程读写共享内存时资源访问的问题。 内存模型...
    99+
    2022-11-12
  • Java并发中的内存模型
    这篇文章主要讲解了“Java并发中的内存模型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中的内存模型”吧!CPU和内存在讲JMM之前,我想先和大家聊聊硬件层面的东西。大家应该都...
    99+
    2023-06-02
  • Java 中的io模型详解
    目录1. BIO2. NIO3. 多路复用最后再说几个小问题1. BIO 我们先看一个 Java 例子: package cn.bridgeli.demo;   imp...
    99+
    2022-11-12
  • JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
    JVM(Java虚拟机) JVM 内存模型 结构图 jdk1.8 结构图(极简) jdk1.8 结构图(简单) JVM(Java虚拟机): 是一个抽象的计算模型。如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。...
    99+
    2023-08-30
    jvm GC 直接内存 jvm性能调优
  • 详解Java中的原型模式
    本篇文章为大家展示了详解Java中的原型模式,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。类型:创建类模式类图:原型模式主要用于对...
    99+
    2023-05-31
    java 原型模式 ava
  • 关于java中堆内存与栈内存的详细分析
    一、概述在Java中,内存分为两种,一种是栈内存,另一种就是堆内存。二、堆内存什么是堆内存?堆内存是Java内存中的一种,它的作用是用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,...
    99+
    2017-04-01
    java入门 java 堆内存 栈内存 分析
  • Golang函数的内存模型和并发编程的关系和模式
    Golang函数的内存模型和并发编程的关系和模式Golang(Go)是一种新兴的编程语言,它以简单、高效和并发编程为特色。在Golang中,函数是一等公民,因此理解其内存模型对于正确使用和优化性能至关重要。随着计算机硬件的发展,多核和分布式...
    99+
    2023-05-16
    Golang 并发编程 内存模型
  • Java内存模型之重排序的相关知识总结
    目录一、数据依赖性二、as-if-serial语义三、程序顺序规则四、重排序对多线程的影响一、数据依赖性 如果两个操作访问同一个变量,而且这两个操作中有一个操作为写操作,此时这两个操...
    99+
    2022-11-12
  • Java常见知识点中Jvm内存结构、Java内存模型、Java对象模型的区别是什么
    这篇文章将为大家详细讲解有关Java常见知识点中Jvm内存结构、Java内存模型、Java对象模型的区别是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。我们都知道,Java代码是要运行在...
    99+
    2023-06-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作