首页 > 编程语言 >java中 Happens-Before 原则

java中 Happens-Before 原则

时间:2024-01-15 19:57:32浏览次数:27  
标签:Happens 缓存 java 规则 线程 JMM happen Before before

前言

并发问题有三个根本原因:

  1. cpu 缓存导致可见性问题
  2. 线程切换导致原子性问题:线程切换是发生于任何一条cpu指令级别的,而不是高级语言中的语句,例如 i++ 是三个cpu指令
  3. 编译器优化导致有序性问题

CPU缓存导致可见性问题与Java内存模型(JMM)的问题实际上是两个相互关联的概念。
CPU缓存带来的问题:
在现代多核CPU架构中,每个核心都可能有自己的一级(L1)和二级(L2)缓存,有时甚至有自己的三级(L3)缓存或者共享L3缓存。
当多个CPU核心上的线程分别操作它们各自缓存中的数据副本时,如果没有适当的同步机制,就可能导致一个核心上的线程所做的更改无法及时对其他核心上的线程可见。这就是所谓的"可见性问题"。

java内存模型(JMM):
JMM是Java语言提供的一个抽象概念,用于定义线程如何与主内存以及彼此之间交互。
JMM知道底层硬件(如CPU缓存)可能会导致并发问题,因此它为开发者提供了规则和机制(如volatile关键字、synchronized块、Locks等)来保证在多线程环境下的操作可见性和有序性。
JMM的目标是在保证程序执行效率的同时,提供一种机制来处理由于缓存和其他硬件优化导致的可见性和重排序问题。
因此,虽然可见性问题是由CPU缓存架构导致的,但JMM并不是问题的根源,而是Java提供的一种解决方案。JMM的设计是为了让Java程序员不必直接处理底层硬件的复杂性,而是通过JMM定义的规则和同步机制来解决这些问题。
如果没有JMM,开发者就需要直接处理底层硬件(如CPU缓存)的细节,这会大大增加并发程序的复杂性和出错几率。JMM提供的抽象层允许开发者在编写并发程序时,可以依靠明确的规则来预测并控制线程间的交互。

Happens-Before 原则

在Java内存模型(JMM)中,"happen-before" 原则是一组规则,用以确定多线程程序中的内存可见性和操作顺序。这个原则是为了解决并发编程中的两大问题:线程之间的可见性(一个线程对共享变量所做的修改何时对其他线程可见)和指令重排序(编译器和处理器为了优化性能而做的指令乱序执行)。

导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但是这样问题虽然解决了,我们程序的性能可就堪忧了。

合理的方案应该是按需禁用缓存以及编译优化。那么,如何做到“按需禁用”呢?对于并发程序,何时禁用缓存以及编译优化只有程序员知道,那所谓“按需禁用”其实就是指按照程序员的要求来禁用。所以,为了解决可见性和有序性问题,只需要提供给程序员按需禁用缓存和编译优化的方法即可。

Happens-Before 原则要表达的意思就是前面一个操作的结果对后续操作是可见的

  1. 程序顺序规则:在同一个线程中,按照程序代码顺序,前面的操作happen-before于后续的操作。

  2. 管程中锁规则:对一个锁的解锁happen-before于随后对这个锁的加锁。

  3. volatile变量规则:对一个volatile域的写操作happen-before于任意后续对这个volatile域的读操作。

  4. 传递性:如果操作A happen-before操作B,操作B happen-before操作C,那么操作A happen-before操作C。

  5. 线程启动规则:Thread对象的start()方法happen-before于此线程的每一个动作。

  6. 线程终结规则:线程中的所有操作都happen-before于线程的终结检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终结。

  7. 线程中断规则:对线程interrupt()的调用happen-before于被中断线程的代码检测到中断事件的发生。对象终结规则:一个对象的初始化完成(构造函数执行完成)happen-before于它的finalize()方法的开始。

  8. 对象终结规则:一个对象的初始化完成(构造函数执行完成)happen-before于它的finalize()方法的开始。

其中最常用的也就是第 1 2 3 4 条

程序顺序规则

这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。比较容易理解。

  int a = 2; // 0
  int b = a + 1; // 1

这里根据 happen-before 原则可以保证 1 式中的 a变量值一定为2。看下面的代码,两个语句没有依赖关系,可能发生指令重排,b有可能先于a 赋值

  int a = 2; // 0
  int b = 4; // 1

管程中锁规则

这条规则是指对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。

  synchronized(this){
    if(this.val <10){
	 this.val = 1;
	}
  }

线程 A 执行完代码块中的代码后,val 的值变为10,线程 B 进入代码时,能够看到线程 A对变量 val 的写操作,也就是它能看到 val ==10;

volatile变量规则

这条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。

标签:Happens,缓存,java,规则,线程,JMM,happen,Before,before
From: https://www.cnblogs.com/hkz329/p/17966125

相关文章

  • SparkStreaming in Java
    参考地址:SparkStreamingProgrammingGuide1.新建Maven项目,POM引入依赖<dependency><groupId>org.apache.spark</groupId><artifactId>spark-streaming_2.13</artifactId><version>3.5.0</ve......
  • Java中的ThreadLocal和 InheritableThreadLocal
    Java中的ThreadLocal和InheritableThreadLocalpackagecom.example.core.mydemo.java;/***output*Thread-0ThreadLocalvalue:null*Thread-0InheritableThreadLocalvalue:InheritableThreadLocalstring*/publicclassThreadLocalTest{publicstati......
  • 【Java 进阶篇】使用 Stream 流和 Lambda 组装复杂父子树形结构(List 集合形式)
    目录前言一、以部门结构为例1.1实体1.2返回VO1.3具体实现1.4效果展示二、以省市县结构为例2.1实体2.2返回VO2.3具体实现2.4效果展示三、文章小结前言在最近的开发中,一星期内遇到了两个类似的需求:返回组装好的部门树、返回组装好的地区信息树,最终都需要返回List集合对象给前端......
  • Java的类加载机制
    Java的类加载机制是指在Java程序运行时,将类文件加载到内存中的一系列步骤。Java的类加载机制遵循着“按需加载”的原则,也就是说,只有在需要用到某个类的时候,才会将这个类的相关信息加载到内存中。这种“按需加载”的设计使得Java程序具备了很好的灵活性和效率。Java的类加载器......
  • Vue 项目离线安装 ArcGIS for JavaScript
    注意:arcgis-js-api在4.18及之后版本,可以通过npminstall@arcgis/[email protected]直接安装在写些博客时,npm能安装的最新版为4.28.10,下面以4.28.10为例,讲解离线安装。在vue3项目中,通过npminstall@arcgis/[email protected]安装,但默认是半本地化的,因为assests资源是通过https://js.ar......
  • java Flink 校验接口数据
    要使用Java编写Flink程序来校验接口的数据,可以按照以下步骤进行操作。首先,需要引入相关依赖包。在pom.xml文件中添加如下依赖项:org.apache.flinkflink-streaming-java_2.12{FLINK版本号}其中{FLINK版本号}应该被替换为所使用的Flink版本号。创建一个新的Java类,并导入必要......
  • 精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原
    背景介绍在Java编程中,动态代理的应用非常广泛。它被广泛应用于SpringAOP框架、Hibernate数据查询、测试框架的后端mock、RPC以及Java注解对象获取等领域。静态代理和动态代理与静态代理不同,动态代理的代理关系是在运行时确定的,这使得它在灵活性上更胜一筹。相比之下,静态代理的代理......
  • Java小细节之数组什么情况下相等,什么情况下不相等
    int[]a={1,2,3};int[]b=a;System.out.println(a==b);此时输出trueint[]a={1,2,3};int[]b={1,2,3};System.out.println(a==b);此时输出为false这是因为数组的机制,int[]b=a,相当于让b和a同时管理这个数组,a和b都是代表同一个数组,所以a==b是正确的,此时对数......
  • SparkSQL 操作Hive In Java
    本文的前提条件:SparkSQLinJava1.增加POM依赖<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.33</version></dependenc......
  • 【JaveWeb教程】(2)Web前端基础:JavaScript入门不再难:一篇文章教你轻松搞定JavaScript的
    目录1介绍2引入方式3基础语法3.1书写语法3.2变量3.3数据类型和运算符4函数4.1第一种定义格式4.2第二种定义格式html完成了架子,css做了美化,但是网页是死的,我们需要给他注入灵魂,所以接下来我们需要学习JavaScript,这门语言会让我们的页面能够和用户进行交互。1介绍通过代......