Java并发编程系列之五:happens-before原则

简介:

前言

happens-before是JMM的核心,之所以设计happens-before,主要出于以下两个方面的因素考虑的:1)程序员的角度,JMM内存模型需要易于理解、易于编程;2)编译器和处理器的角度,编译器和处理器希望内存模型对其束缚越少越好,这样就可以根据自己的处理规则进行优化。但是这两个方面其实是相互矛盾的,因为JMM易于编程和理解就意味着对编译器和处理器的束缚就越多。

happens-before定义

基于上面的考虑,设计JMM时采用了一种折中的选择——JMM将需要禁止的重排序分为两类(因为编译器和处理器的优化大部分是重排序,所以JMM的处理的关键也就是重排序了):

  • 会改变程序执行结果的重排序
  • 不会改变程序执行结果的重排序

对应这两种情况,JMM采用了不同的策略:

  • 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序
  • 对于不会改变程序执行结果的重排序,JMM对编译器和和处理器不做任何要求(自然,编译器和处理器可以其进行重排序)

所以JMM的设计基于这样一种原则:先保证正确性,在考虑执行效率问题

说了这么多,与happens-before原则有什么关系呢?从上面可以看到JMM实际上可以看做是操作之间的约束模型,这种约束模型的实现就是我们要提到的happens-before了。happens-before**用来指定两个操作之间的执行顺序**,这两个操作可以在一个线程之内也可以在不同的线程中,所以这种对操作顺序的关系的界定可以为程序员提供内存可见性的保证。具体happen-before的定义如下:

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。

上面第二句话的意思就是说,如果重排序之后的执行结果与按照原来那种happens-before关系执行的结果一致,那么JMM允许编译器和处理器进行这种重排序。所以可以认为:只要不改变程序的执行结果,编译器和处理器可以随意优化。联系之前提到的as-if-serial语义(保证单线程内的程序执行结果不会改变),现在提到的happens-before则保证正确同步的多线程的执行程序的执行结果不会发生改变。

happens-before规则

总共有六条规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于随后该线程中的任意后续操作
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的获取
  • volatile变量规则:对一个volatile域的写,happens-before于对这个变量的读
  • 传递性:如果A happens-before B,B happens-before C,那么A happens-before C
  • start规则:如果线程A执行线程B的start方法,那么线程A的ThreadB.start()happens-before于线程B的任意操作
  • join规则:如果线程A执行线程B的join方法,那么线程B的任意操作happens-before于线程A从TreadB.join()方法成功返回。

看看start规则,假设这样一种情况,如果线程A在执行线程B的start方法之前修改了一些共享变量的值,那么当线程B执行start方法的时候,会去读取这些修改的共享变量的值(上面规则就是这么规定的),这就意味着线程A对共享变量的修改对线程B可见

下面看看join规则是怎么回事。join方法的本义是等待当前执行的线程终止。假设在线程B终止之前,修改了一些共享变量(完全可能啊),线程A从线程B的join方法成功返回后,就会读取这些修改的共享变量。这样也保证了线程B对共享变量的修改对线程A是可见的。

目录
相关文章
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
12 2
|
6天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
35 5
|
1天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
78 53
|
1天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
15 1
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
30 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
26 3
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
5天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
20 2
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1