Java内存模型与Hppens-Before规则

Java内存模型与Hppens-Before规则

为什么要有 Java内存模型?

并发编程的3个源头问题分别是:

  • 可见性,由缓存导致的可见性问题
  • 有序性,由编译优化导致的有序性问题
  • 原子性,由线程切换导致的原子性问题

Java内存模型就是为了解决可见性和有序性问题。

什么是 Java 内存模型?

缓存会导致可见性问题,编译优化会导致有序性问题。如果要避免这两个问题,最简单的方法不就是禁用缓存和编译优化。这样就丢掉了优化程序性能的有利武器,显然是不可取的。

合理的方案应该是按需禁用缓存以及编译优化。什么叫按需禁用缓存以及编译优化呢?指的就是程序员在写代码的过程中,对有可能出现并发问题的代码禁用缓存和编译优化。

Java内存模型就是禁用缓存和编译优化的一种规范,它规范了 JVM 如何提供按需禁用缓存和编译优化的方法。现在提到的 Java 内存模型,一般指的是 JDK 5 开始使用内存模型,遵循的是 JSR-133 描述的规范。

JSR133

Java内存模型是一个雄心勃勃的计划,它是编程语言规范第一次尝试合并一个能够在各种处理器架构中为并发提供一致语义的内存模型。不过,定义一个既一致又直观的内存模型远比想象要更难。JSR133 为Java语言定义了一个新的内存模型,它修复了早期内存模型中的缺陷。为了实现 JSR133,final和volatile的语义需要重新定义。

完整的语义见:http://www.cs.umd.edu/users/p...,但是正式的语义不是小心翼翼的,它是令人惊讶和清醒的,目的是让人意识到一些看似简单的概念(如同步)其实有多复杂。幸运的是,你不需要懂得这些正式语义的细节——JSR133的目的是创建一组正式语义,这些正式语义提供了volatile、synchronzied和final如何工作的直观框架。

—— Java内存模型FAQ(三)JSR133是什么?

Java内存模型主要分成两部分,一部分面向并发编程的开发人员,一部分面向JVM开发人员,我们需要关注的是前者。前者主要包括 volatilesynchronizedfinal 三个关键字,以及六项 Happens-Before 规则。

Happens-Before 规则

Java内存模型是按需禁用缓存和编译优化的规则。Happens-Before 表示前面一个操作对后面一个操作是可见的,它约束了编译器的优化行为,编译器可以优化,但是需要遵循 Happens-Before 规则。

有这样 6 条和可见性相关的规范:

  • 程序的顺序性规则:按照程序顺序,前面的操作 Happens-before 于后面的操作。
  • volatile 变量规则:对 volatile 变量的写操作,Happens-before 于对该 volatile 变量的读操作。
  • 传递性:若 A Happens-Before B,B Happens-Before C,则 A Happens-Before C。
  • 管程中的锁规则:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
  • 线程 start() 规则:主线程 A 启动子线程 B,则子线程 B 能看到主线程 A 在启动子线程 B 之前的操作。
  • 线程 join() 规则:主线程 A 等待子线程 B 完成后,主线程能看到子线程 B 操作

Happens-Before规则对 volatile 语义的增强

前面提到过,现在所说的 Java 内存模型一般指的是 Java1.5 之后的内存模型,它遵循 JSR-133 描述的规范。采用新的规范的原因是旧的 Java 内存模型存在问题,比如:对 final 修饰的变量进行过度的编译优化,以及 volatile 的语义问题。

// 以下代码来源于【参考1】
class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

上面这段代码,变量 v 是一个 volatile 类型的变量,则会禁用缓存,所有线程对 v 的操作都是直接从内存中读取。此时,线程 A 执行 writer() 方法,则 v = true 会被写入内存;同时线程 B 执行 reader() 方法,显然会通过 if 判断,那么此时 x 的值会是多少?

在 Java1.5 之前,x 的值可能是 0,也有可能是 42。因为 x 有可能被 CPU 缓存起来,导致可见性问题。这显然不是我们期望的情况,所以在新的 Java 内存模型中对 volatile 的语音进行了增强,就是依靠 Happens-Before 中的 volatile 变量规则和传递性规则。

volatile语义增强

如上图所示,线程 A 执行 writer() 方法,线程 B 执行 reader() 方法:

  • 根据顺序性规则,x=42 happens-before 于写变量 v;读变量 v happens-before 于读变量 x
  • 根据volatile变量规则,对 volatile 变量的写操作 happens-before 于对该变量的读操作
  • 根据传递性规则,x=42 happens-before 于读变量 x

此时,通过 Happens-Before 规则对 volatile 变量的语义增强,线程 B 读到的 x 就一定是 42,而不会存在是 0 的情况,符合我们对程序的期待。

相关文章

Java内存模型FAQ

JSR 133 (Java Memory Model) FAQ

再有人问你Java内存模型是什么,就把这篇文章发给他。

02 | Java内存模型:看Java如何解决可见性和有序性问题

Java内存模型(JMM)详解

Java内存模型相关原则详解

Image placeholder
aloha
未设置
  13人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Go语言高级编程_1.5 面向并发的内存模型

1.5面向并发的内存模型 在早期,CPU都是以单核的形式顺序执行机器指令。Go语言的祖先C语言正是这种顺序编程语言的代表。顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有且仅有

Go语言高级编程_2.7 CGO内存模型

2.7CGO内存模型 CGO是架接Go语言和C语言的桥梁,它使二者在二进制接口层面实现了互通,但是我们要注意因两种语言的内存模型的差异而可能引起的问题。如果在CGO处理的跨语言函数调用时涉及到了指针的

css before显示不出来怎么办

cssbefore显示不出来怎么办cssbefore伪类元素要显示,需要设置content属性,而且要具有一定的宽高,可以设置display为inline-block和block,让元素的宽高生效。下

基于内存和文件存储的 queue worker, 不用 Redis 适合单进程使用没有外部依赖

因为最近要做一个简单的并发任务系统,在github上面找了一圈并没有简单可依赖的库,所以自己写了一个。欢迎大家Review贡献代码。项目地址https://github.com/iflamed/mfw

100%数据可用性承诺 VSP 5000系列如何改变存储行业规则

上个月,HitachiVantara在于拉斯维加斯举行的NEXT2019大会上,发布了其最新的企业级高端存储系统VSP5000系列产品。通过这款全面提升的企业级闪存阵列,致力于提供业界领先的性能和弹性

css绘制不规则三角形

css绘制不规则三角形css绘制不规则三角形原理:一个盒子包括:margin+border+padding+content上下左右边框交界处出呈现平滑的斜线.利用这个特点,通过设置不同的上下左右边框宽

css的语法规则是什么?

css的语法规则是什么?1、CSS规则由两个主要的部分构成:选择器,以及一条或多条声明:选择器{ 声明1; 声明2; 声明3; ...... }(推荐学习:CSS视频教程)2、CSS样式申明——At-

Hyperf 权限管理组件 hyperf-permission 发布

本人正在申请版主,还望各位多评论,收藏,点赞GITHUB:https://github.com/donjan-deng/hyperf-perm...欢迎star,欢迎pr.Hyperf权限管理组件sp

vue引入swiper vue使用swiper vue脚手架使用swiper /引入js文件/引入css文件

vue引入swipervue使用swipervue脚手架使用swiper/引入js文件/引入css文件欢迎加入前端交流群来获取视频资料以及前端学习资料:749539640转载文章请注明出处! 如果只是

MVVM原理(Object.defineProperty和订阅者模式)

想着去了解vue的mvvm数据驱动是怎么实现的,百度中看了这篇文章,demo很好。其他文章只是讲到defineProperty的set,get。彻底理解Vue中的Watcher、Observer、De

记一次 vue 的异步更新队列导致内存泄漏

起因 由于项目是需要连续传输图片形成一个伪视频(没办法,客户钱给的不够)来观看。后端采用传输base64的图片到前端展示。 环境 php:7.2 workerman:3.X vue:2.X 过程 wo

Go内存分配跟踪调优

今天小编为大家分享一篇关于Go内存分配跟踪调优的文章,文中涉及到一些压测及跟踪分析的工具,以及问题查找方法,希望能对大家有所帮助。Makeitwork,makeitright,makeitfast.–

共享内存在不同系统的应用与优劣详解

共享内存是一种使计算机程序能够同时共享内存资源以实现更高性能和更少冗余数据副本的技术。共享系统内存可以在单处理器系统、并行多处理器或集群微处理器上运行。对于分布式系统会有一些差异,但共享内存也可以其上

多进程之间的线程利用XSI IPC共享内存分配互斥量进行同步

···#include#include#include#include#include#include#include#include#include#definehandle_error_en(en

自动识别Android不合理的内存分配

写在前面Android开发中我们常常会遇到不合理的内存分配导致的问题,或是频繁GC,或是OOM。按照常规的套路我们需要打开AndroidStudio录制内存分配或者dump内存,然后人工分析,逐个排查

Java内存映射,上G大文件轻松处理

内存映射文件(Memory-mappedFile),指的是将一段虚拟内存逐字节映射于一个文件,使得应用程序处理文件如同访问主内存(但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作),

面试题:请解释一下什么是虚拟内存?

内存对于用户来说就是一个字节数组,我们可以根据地址来访问到某个字节或者某些字节:很久之前的内存很久很久之前,一台机器上只放置一个程序,操作系统仅仅作为一个函数库存在。对于内存来说,除去操作系统的代码和

Kafka 如何优化内存缓冲机制造成的频繁 GC 问题?

目录1、Kafka的客户端缓冲机制2、内存缓冲造成的频繁GC问题3、Kafka设计者实现的缓冲池机制4、总结一下“ 这篇文章,给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来

打破边界 不是所有“内存与存储”都叫傲腾

人类正在向一个万物感知、万物互联、万物智能的世界进化。一方面海量的数据对数据基础设施带来了新的挑战;另一方面伴随着数据中心业务和应用的多样化以及智能化,企业对数据存储的需求越来越高。智能世界的特点是能

谁创建谁销毁,谁分配谁释放——JNI调用时的内存管理

在QQ音乐AndroidTV端的Cocos版本的开发过程中,我们希望尽量多的复用现有的业务逻辑,避免重复制造轮子。因此,我们使用了大量的JNI调用,来实现Java层和Native层(主要是C++)的代

深入理解JVM - 内存溢出实战

Java堆溢出Java堆用于存储对象实例,只要不断地创建对象,当对象数量到达最大堆的容量限制后就会产生内存溢出异常。最常见的内存溢出就是存在大的容器,而没法回收,比如:Map,List等。出现下面信息

JVM内存布局

   JVM中将内存分为若干部分:堆、方法区、虚拟机栈、本地方法栈、程序计数器             程序计数器:该区域是内存中较小的一块区域---是当前线程在执行的字节码的行号指示器。程序计数器是

红帽OpenShift得到IBM、AWS和Azure的支持,生态能力正不断扩大

继IBM在11月6日宣布,IBMCloudPaks容器云的底层技术通过红帽OpenShift来支持后;AWS也于11月7日表示,原生集成AWS服务的红帽OpenShift容器平台已可用于由光环新网技术

《从PPTV网络视频,到PPIO区块链分布式存储》

摘要:2019年11月26日,同济创业谷与PPIOCodeTalks联合举办了《创新X-区块链与创新创业》区块链技术分享会,本期我们为读者带来主题分享--《从PPTV网络视频,到PPIO区块链分布式存

Pandas数据处理三板斧——map、apply、applymap详解

微信公众号:「Python读财」如有问题或建议,请公众号留言在日常的数据处理中,经常会对一个DataFrame进行逐行、逐列和逐元素的操作,对应这些操作,Pandas中的map、apply和apply