关于jmm:JMM-学习笔记一-跨平台的JMM

48次阅读

共计 13558 个字符,预计需要花费 34 分钟才能阅读完成。

前言

其实对于并发、多线程这方面的文章,我曾经写过了一些:

  • 《当咱们说起多线程与高并发时》
  • 《Java 多线程学习笔记(一) 初遇篇》
  • 《Java 多线程学习笔记(二) 相识篇》
  • 《Java 多线程学习笔记(三) 甚欢篇》
  • 《Java 多线程学习笔记(五) 长乐无极篇》
  • 《Java 多线程学习笔记(六) 长乐未央篇》
  • 《Java 多线程编程范式(一) 合作范式》

《当咱们说起多线程与高并发时》是总领全篇,咱们从操作系统的倒退讲起,为什么要有线程这个概念呈现。《Java 多线程学习笔记(一) 初遇篇》讲 Java 平台下的线程,如何应用和创立,以及引入线程后所面临的问题,为了解决线程平安问题,Java 引入的机制,这也是《Java 多线程学习笔记(二) 相识篇》探讨的问题,《Java 多线程学习笔记(三) 甚欢篇》是讲线程合作,即如何让线程之间合作去解决工作,《Java 多线程学习笔记(五) 长乐无极篇》讲了 CompletableFuture,这个弱小的异步编排组件,《Java 多线程学习笔记(六) 长乐未央篇》讲 ForkJoin 模式,《Java 多线程编程范式(一) 合作范式》讲应用 Java 提供并发外围库来解决一些问题。然而在《Java 多线程学习笔记(一) 初遇篇》咱们的探讨绝对还比拟毛糙,过后我的想法是先根本搭建一个模型来疾速的相熟 Java 的并发编程,在实践中先用起来,咱们没有间接探讨线程平安,什么是线程平安,这个问题在过后的我去看,没有找到一个很完满的定义,还有并发模型,并发是难以验证的,那咱们该如何验证,咱们将对立收拢,对立答复这些问题。

从指令集架构谈起

单看指令集架构来说,这是一个有些绝对生疏的名词,让咱们从生存中略微常见的事物讲起,也就是苹果电脑 Mac,很多程序员都喜爱 Mac,Mac 中当初比拟热的一款是 Mac m1、m2 了,喜爱苹果的人,对 m1 和 m2 相当喜爱,这里说的 m1 和 m2 也就是 CPU 的代称,这两款 CPU 的指令集架构是 ARM,那什么是指令集架构?在答复这个问题的时候,咱们还是要请出《程序是如何运行的(一)》这篇文章的图:

从这幅图咱们能够看到指令集架构是硬件零碎和软件的桥梁,连贯了硬件和软件。那他是什么呢?

An Instruction Set Architecture (ISA) is part of the abstract model of a computer that defines how the CPU is controlled by the software. The ISA acts as an interface between the hardware and the software, specifying both what the processor is capable of doing as well as how it gets done.

指令集架构是计算机形象模型的一部分定义了 CPU 如何被软件管制。指令集充当硬件和软件之间的接合点,规定了处理器可能做什么,以及如何实现。

The ISA provides the only way through which a user is able to interact with the hardware. It can be viewed as a programmer’s manual because it’s the portion of the machine that’s visible to the assembly language programmer, the compiler writer, and the application programmer.

指令集是用户和计算机之间进行交互的惟一路径。它能够被看做是程序员的手册,因为它是程序员(汇编语言程序员,编译器开发者、应用程序程序员) 能够看到的机器局部。

The ISA defines the supported data types, the registers, how the hardware manages main memory, key features (such as virtual memory), which instructions a microprocessor can execute, and the input/output model of multiple ISA implementations. The ISA can be extended by adding instructions or other capabilities, or by adding support for larger addresses and data values.

指令集架构定义了数据类型、寄存器、硬件如何治理主存,要害个性(如虚拟内存),微处理器能够执行哪些指令,输入输出模型。指令集架构能够通过减少指令、其余性能、减少对更大地址和数据值的反对来进行扩大。

–ARM 官网

这里提到了数据类型,我想起学 Java 的时候,老师谈到数据类型的时候说,你家左近开了一家水果店,卖西瓜简直不要钱,大家都拿货色去买,有的人拿大袋子,有的人拿小袋子,你会拿什么去装,因为我十分爱吃西瓜,所以我说的是我会开辆车去装,简直不要钱嘛。其实老师讲这个例子,只是想引出数据类型是数据的容器。我又想起小的时候吃饭,饭量小的用小碗,饭量大的用大碗。

那设计一个编译器来说,个别的思路就是先引入对应的数据类型,那数据类型看来还是来自于指令集架构的反对,已知 Java 是跨平台的,这个平台咱们能够了解为指令集架构,从 Github 上 OpenJDK 的源码能够看出:

那么 Java 一共反对了七种指令集架构:aarch64、arm、ppc、riscv、s390、x86、zero。这里简略的介绍一下指令系统,随着技术的提高,计算机的状态产生了微小的变动,从巨型机到小型机到个人电脑,再到智能手机,其根底元件从电子管到晶体管再到超大规模的集成电路。尽管计算机的状态和利用组合变幻无穷,但从用户感知的应用软件到最底层的物理载体,计算机系统均呈现出档次的构造。下图展现了这些档次:

从上到下,计算机系统可分为四个档次,别离为应用软件、根底软件、硬件电路和物理载体。软件以指令模式运行在 CPU 硬件上,而指令系统介于软件和硬件之间,是软硬件交互的接口(这里援用的是《计算机体系结构根底》原文用的是界面,我想是用错了词,该当了解为接口更为得体),有着十分要害的作用。软硬件自身更迭速度很快,而指令系统则能够放弃很长时间的稳固。有了稳固不变的指令系统接口,软件与硬件失去无效的隔离,并行倒退。遵循同一指令系统的硬件能够运行为该指令系统设计的各种软件,比方 X86 计算机既能够运行最新软件,也能够运行 30 年前的软件(这一句话也援用自《计算机体系结构根底》,然而我的了解倒是与此不统一,X86 计算机能够运行 30 年前的软件,得益于两方面,一方面是指令系统的向前兼容,另一部分则源于操作系统的向前兼容)。根据指令长度的不同,指令系统可分为简单指令系统(Complex Instruction Set Computer,简称为 CISC),精简指令零碎(Reduced Instruction Set Computer, 简称 RISC) 和 超长指令字(Very Long Instruction Word, 简称 VLIW) 指令集三种。晚期的 CPU 都采纳 CISC 构造,这与过后的时代特点无关,晚期的解决设施低廉且处理速度迟缓,设计者不得不退出越来越多的简单指令来进步执行效率,局部简单指令甚至可与高级语言中的操作间接对应。这种设计简化了软件和编译器的设计,但也显著进步了硬件的复杂性。

随着硬件的倒退,CISC 构造呈现了一系列问题。大量简单指令在理论中很少用到,典型程序所应用的的百分之八十指令只占到指令集总指数的百分之二十,耗费大量精力的简单波及只有很少的回报。针对 CISC 构造的毛病,RISC 遵循简化的外围思路,RISC 简化了指令性能。下面的 ARM 指令集架构就是 RISC 架构,ARMv8- A 引入了 64 位架构,咱们称之为 Aarch64,在目前来看它们都指同一事物,也就是公版 64 位 ARMv8 当前的所有 64 位 ARM 架构。那 X86 是 RISC 还是 CISC,以前我认为是 CISC 架构,然而我在《计算机体系结构根底》中看到这么一句话:

X86 处理器中将 CISC 指令译码为类 RISC 的外部操作,而后对这些外部操作应用诸如超流水、乱序执行,多发射等高效实现伎俩。

所以 X86 指令集架构这么一看,也不能齐全算作 CISC 架构,CISC 和 RISC 有点走向交融的感觉。PPC 是 Power PC 的缩写,基于 RISC。RISC- V 从名字就能够看出基于 RISC。S390 没查到材料,Zero 没有指令系统(没有指令系统引自维基百科,对这个我也不足认知,),材料太少,不晓得是怎么运作的。

那么不同的指令集反对的指令就是不同的,而 JVM 是一个跨平台的虚拟机,Java 是一个跨平台的语言,也就意味着 Java 要保障在适配的平台上,行为统一。

CPU 内存模型

浅谈模型

CPU 这个词咱们意识,内存这个词咱们也意识,那模型呢,当咱们说起模型这个词的时候,咱们到底在说什么?当我说起模型这个词的时候,我想到的是预测,我想起的是预测,形象出运行法则,依据运行法则来进行预测,这让我想起高中生物教材的 K 值曲线:

[]

这事实上是一种数学模型,那什么是数学模型?一般地说,数学模型能够形容为,对于事实世界的特定对象,为了一个特定目标,依据特有的外在法则,作出一些必要的简化建设,使用适当的数学工具,失去的一个数学构造。其实构造这个词,如果对我文章有些相熟的话,其实这个词曾经探讨过很多遍了, 这里再讨论一下:

the arrangement of and relations between the parts or elements of something complex.

复杂事物的各个局部或因素之间的安顿和分割。

下面的种群增长数学模型,形容的就是在资源和空间无限,天敌的制约等 (即存在环境阻力) 的状况下,工夫与种群数量之间的关系。数学模型最终是为了失去一个关系,那内存模型呢?数学模型和内存模型都是模型,那该怎么了解模型这个词呢?

模型是指对于某个理论问题或客观事物、法则进行形象后的一种形式化表达方式。

那么 CPU 内存模型就是对某个理论的读写问题进行形象后的一种形式化表达方式,那到底是遇到了怎么的问题呢? 让咱们从冯诺依曼计算机模型讲起,冯诺依曼计算机模型是一种将程序指令存储器和数据存储器并在一起的计算机设计概念构造。依据冯诺依曼结构设计进去的计算机咱们称作冯诺依曼计算机,又称存储程序计算机。计算机在运行指令的时候,会从存储器中一条条将指令取出,通过译码(控制器),从存储器中取出数据,而后进行指定的运算和逻辑等操作,而后再按地址把运算后果返回内存中取。接下来,再取出下一条指令,在控制器模块中依照规定查找。顺次进行上来。直至遇到进行指令。程序与数据一样存储,依照程序编排的程序,一步一步地取出指令按规定操作,主动地实现指令规定的操作是计算机最根本的工作模型。上面这张图是 Intel 零碎的硬件构造:

个别咱们看 CPU 的性能,个别都是看主频,主频也被称之为时钟速度,那什么是时钟速度:

CPU 每秒要解决来自不同程序的泛滥指令(如算术等低级计算)。时钟速度则测量 CPU 每秒执行的周期数,以 GHz(千兆赫)为单位。从技术上讲,“周期”是由外部振荡器同步的脉冲,但就咱们的目标而言,它们是帮忙了解 CPU 速度的根本单位。在每个周期中,处理器内数十亿个晶体管会关上和敞开。时钟速度为 3.2 GHz 的 CPU 每秒执行 32 亿个周期。(较早的 CPU 的速度以兆赫计算,或每秒几百万个周期。有时,多个指令可在一个时钟周期内实现;而在其余状况下,一条指令可能须要多个时钟周期来解决。因为不同的 CPU 设计解决指令的形式不同,所以最好比拟同一品牌和同一代 CPU 的时钟速度。

例如,5 年前时钟速度更高的 CPU,其性能可能还不如时钟速度更低的新 CPU,因为新架构能够更高效地解决指令。英特尔® X 系列处理器的性能可能优于时钟速度更高的 K 系列处理器,因为它能够在更多的内核之间分配任务,并具备更大的 CPU 缓存。然而,在同一代 CPU 中,在许多利用方面,时钟速度较高的处理器通常优于时钟速度较低的处理器。因而,请务必对同一品牌和同一代系的处理器进行比拟。

—Intel 官网, 参看参考文档[13]

我这里来补充介绍一下,时钟周期是计算机中最根本的、最小的工夫单位,在一个时钟周期内,CPU 仅实现一个最根本的操作。我笔记本的 CPU 主频为 2.3GHZ,那么一秒之内,我的 CPU 就能够执行 23 亿基本操作。依照个别的推理来说,进步 CPU 的运算性能,咱们在晋升架构性能的同时,晋升主频就能够了,也就是一边钻研更高效的解决指令,一边钻研怎么在高效的架构更加疾速的晋升时钟速度。然而遗憾的是,咱们并不能主频不能无限度的被晋升, 起因在于主频进步过了一个拐点之后,功耗会爆炸增长,进步主频这条路走不通,那就再加一个处理器,这也就是多核处理器。

多核处理器和并发工作的呈现

引入了多核处理器之后,能够持续晋升 CPU 的性能了,然而又引入了新的问题,这没方法,在计算机世界外面,没有银弹,可能应酬所有状况。在多核 CPU 状况下,计算机的内存构造能够被下图示意:

数据从主内存一级一级的加载到 CPU,实现指令之后,再将计算结果写入对应的内存地址,其实这里还漏了磁盘,咱们权且疏忽。晚期的计算机是独占式的,

像下面的图片一样,一个程序写好了放在纸带上,被读取执行,然而随着硬件的高度集成化倒退,计算机变得能够同时执行多个过程,这某种程度是一种并发,比方我当初写文章用 typora 写,一边用浏览器关上 b 站听歌曲,浏览器和 typora 事实上是两个过程,但给我的感觉就是计算机同时在接管我敲击键盘的指令,一边在驱动我的音响播放音乐,这所有都源于 CPU 弱小的计算速度,这对于用户来说是无感知的,想起我之前写的文章《当咱们说起多线程与高并发时》:

计算机用户通常认为操作系统可能同时做很多事件是无比失常的事件,因为他们通常会在应用办公软件解决文字的时候,其余程序在下载文件,治理打印,解决音频。甚至是一个应用程序也是心愿同时可能做不止一件事。例如,一个音频处理程序必须同时从网络上读取音频,而后解压缩,治理播放,更新进度(这是当初很稀松平时的事件,也就是在线听歌)。不论是文字处理软件有多忙,它也总是在时刻响应键盘和鼠标。可能同时做不止一件事件的软件,咱们称之为并发软件。

下面的并发强调的是同时做不止一件事件,这是一种操作系统提供给程序的假象,事实上他们可能是交替执行的(并发),当然也可能是同时执行的(并行),这取决于以后计算机系统的根本配置和繁忙水平无关。假如以后计算机不是很繁忙,也就是说运行的程序并不多, 又假如 CPU 很弱小, 过程的两个动作(古代操作系统来说个别是线程,古代操作系统调度的根本单位就是线程),就可能会被调配到这两个外围上同时执行。如果此时以后计算机系统相对来说处于一种比拟繁忙的状态,那么他们就只能排队执行,交替执行。

我也想起我之前的某一位 Java 老师,认为没有多线程当初的操作系统只能程序执行程序,计算机只能执行一个过程,这种认识是只站在一个 Java 这一种语言来思考问题,存在肯定的认知舛误,我想起因大略在于大略在于 Java 为人熟知最多的就是多线程 API,这经常给人一种错觉。其实 Java 也提供了创立过程的 API:

private static void createProcess() throws IOException {Runtime runTime = Runtime.getRuntime();
        // 在独自的过程中执行传入的命令
        runTime.exec("");
        ProcessBuilder processBuilder = new ProcessBuilder();
        // 开启一个过程
        processBuilder.start();}

过程和线程都是操作系统提供的概念,操作系统引入过程,是为了并发的执行程序,引入线程则是为了为了更好的共享资源、节俭资源。那么多核碰见并发执行程序就擦出了火花,当多个处理器的运算工作波及同一块主内存区域时,将可能导致各自的缓存数据不统一的状况,为了解决这个问题,就须要制订规定,这也就是缓存一致性协定,这类的协定有 MSI、MESI、MOSI 等等。

MESI 协定简介

当 CPU 写数据时,如果发现操作的变量是共享变量,即在其余 COU 也存在该变量的正本,会发出信号告诉其余 CPU 将变量的缓存行设置为有效状态,因为当其余 CPU 须要读取这个变量时,发现自己缓存中缓存该变量的缓存行是有效的,那么它就会从内存中从新获取。缓存行的中具体的几种状态如下:

咱们当初举个例子来阐明领会一下缓存一致性协定,为了探讨问题不便,咱们当初的处理器只有两外围,也就是两个 CPU, 当初主内存有一个变量 x = 1,MESI 的工作流程为:

  • 假如 CPU1 须要读取 x 的值,此时 CPU1 从主内存中读取到缓存行后的状态为 E,代表只有以后数据中独占数据,并利用 CPU 嗅探机制监听总线中是否有其余缓存读取 x 的操作。
  • 此时如果 CPU2 也须要读取 x 的值到缓存行,则 CPU2 中缓存行的状态为 S,示意多个缓存中共享,同时 CPU1 因为嗅探到 CPU2 也缓存了 x,所以状态也变成了 S。并且 CPU1 和 CPU2 会同时嗅探是否有令缓存生效获取独占的操作。
  • 当 CPU1 有写入操作须要批改 x 的值时,CPU1 中缓存行的状态就变成了 M。
  • CPU2 因为嗅探到了 CPU1 的批改操作,则会将 CPU2 中缓存的状态变为 I 有效状态。
  • 此时 CPU1 中缓存行的状态从新变回独占 E 的状态,CPU2 要想读取 x 的值的话须要从新从主内存中读取。

状态变动图如下:

写缓冲器与有效化队列

MESI 协定解决了缓存一致性问题,然而其本身也存在一个性能弱点 - 处理器执行写内存操作时,必须期待其余所有处理器将其高速中相应正本数据删除接管到这些处理器所回复的音讯之后,能力将数据写入高速缓存。为了躲避和缩小这种期待造成的写操作的提早,硬件设计者引入了写缓冲器和有效化队列。写缓冲器是处理器外部的一个容量比高速缓存还小的部件,每个处理器都有其写缓冲器,一个处理器无奈读取另外一个处理器上的写缓冲器中的内容。

引入写缓冲器之后,处理器在执行写操作的时候会做这样的解决: 如果相应的缓存行状态为 E 或者 M,那么处理器可能会间接将数据写入相应的缓存行而无需发送任何音讯,具体的行为取决于具体处理器的实现。x86 处理器不论相应的缓存条目状态如何,间接先将每一个写操作的后果都存入写缓冲器。如果相应的缓存条目状态为 S,那么处理器会将写操作的相干数据存入写缓冲器的条目之中,并发送有效音讯;如果相应缓存条目标状态为 I,咱们称相应的操作遇到了写未命中,那么此时处理器会将写操作相干的数据存入写缓冲器中,并向主内存发送申请读取音讯,read 音讯可能导致内存读操作,留神是可能,不同的处理器可能有不同的操作,咱们在这里不做赘述,相干的参考文档能够在对应解决的操作手册中能够看到,因而内存读操作的开销是比拟大的。内存写操作的执行处理器在将写操作的相干数据写入写缓冲器之后便认为该写操作曾经实现,即该处理器不会等其余处理器发送的有效音讯和从新读取实现而是继续执行其余指令。

而其余处理器收到有效音讯之后,并不删除音讯中指定地址对应的正本数据,而是将音讯存入有效化队列之后就回复 Invalidate Acknowledge 音讯,从而缩小了写操作执行处理器所需的等待时间。但并非所有处理器都会应用有效化队列。

乱序执行

从 CPU 的角度来看,如果两个指令之间存在显著的关联关系。例如,当从内存中读取一个值,而后将其作为指针拜访另一个内存地址时,CPU 能够推断出这两个操作是存在依赖关系的,不应该被重排序,那么潜台词就是如果两条指令之间不存在依赖关系时,处理器可能会对输出代码进行乱序执行,处理器会在计算之后将乱序执行的后果重组,保障该后果与执行程序是统一的,但不保障各个语句执行的先后顺序和输出的程序统一。大多数处理器不会依赖指令进行重排序,但也有例外,比方 DEC Alpha 处理器,它属于独立的畛域。

猜想执行 Speculative execution

许多古代处理器会采纳猜想执行,其中一种预测执行类型是,处理器可能会预计条件分支被执行的概率,并执行前面的指令。如果猜正确,处理器就更快地执行了指令,如果猜想谬误,处理器会尝试撤销它预测执行指令的成果。这么讲可能有点形象,咱们类比生存中的例子,猜想执行技术就好比没有卫星导航时代在生疏中央开车遇到岔路口的情景: 尽管咱们不确定其中哪条路可能通往目的地,然而咱们能够凭借猜想走其中的一条路,万一猜错了能够掉头从新走另外一条路。猜想执行可能造成 if 语句的语句提前于其条件语句被执行的成果,既可能导致指令重排序。

存储子系统重排序

下面咱们提到了指令重排序,指令重排序的对象是指令,它是实实在在的对指令程序进行调整,而存储子系统重排序重排序是一种景象而不是一种动作,它并没有真正对指令执行程序进行调整,而只是造成了一种指令的执行程序像是被调整过一样的景象,其重排序的对象是内存操作的后果。习惯上为了便于探讨,在论及内存重排序问题的时候咱们往往会采纳指令重排序的形式来表述,即咱们也会用内存操作 X 被重排序到内存操作 Y 之后这样的表述来称说内存重排序。

从处理器的角度来说,读内存操作的本质是从指定的 RAM 地址加载数据,因而这种内存操作也被称为 Load,写内存操作的本质是将数据存储到指定地址示意的 RAM 存储单元中,因而内存操作通常被称为 Store。所以,内存重排序实际上只有以下四种可能:

X86 处理器只存在最初一种问题读取会被从新排序到写入之前,X86 因而被称为具备强内存模型的处理器,其余平台的处理器四个问题都有可能呈现,四个问题都可能呈现的解决被称为弱内存模型处理器。这也就是 CPU 的内存模型。处理器反对哪种内存重排序就会提供可能禁止想要重排序的指令,这些指令就被称为内存屏障 –LoadLoad 屏障、LoadStore 屏障、StoreStore 屏障和 StoreLoad 屏障。

写缓冲器和有效化队列与内存重排序

写缓冲器和有效化队列都可能导致内存重排序,写缓冲器可能导致 StoreLoad 重排序,StoreLoad 是大多数处理器都容许的一种内存重排序。假如处理器 0 和处理器 1 未应用任何同步措施而依照程序程序并按照下标所示的线程交织程序执行。其中 X、Y 为共享变量,其初始值为 0,r1、r2 位局部变量。

当处理器 0 执行到 L2 时,尽管在此之前 S3 曾经被处理器 1 执行结束,然而因为 S3 的执行后果可能依然还停留在处理器 1 的写缓冲器中,而处理器无奈读取另外一个处理器写缓冲器的内容,因而处理器 0 此刻读取到 Y 的值依然是其高速缓存中存储的该变量的初始值 0。同理,处理器 1 执行到 L4 时所读取到变量 X 的值也可能是该变量的初始值 0。因而,从处理器 1 的角度来看,处理器 1 执行 L4 的那一刻处理器 0 曾经执行了 L2 而 S1 却像是尚未执行,即处理器 1 对处理器 0 的两个操作的感知程序是 L2->S1, 也就是说此时写缓冲器导致了 S1 写操作被重排序到 L2 之后。StoreLoad 可能导致某些算法生效,比方 Peterson 的互斥算法。

由内存模型到 Java 内存模型

咱们能够看到,差别是客观存在的,跨平台不是天生的,要做到跨平台,就要弥合差别,保持一致。咱们来总结一下咱们目前遇到的问题,随着计算机技术的飞速发展,软件和硬件都在进化,硬件在进化的路上,发现晋升主频的路线走不通,于是就抉择了多核处理器来晋升性能,而悬浮于硬件的软件则试图不让硬件闲着,引入过程和线程等概念。多核 CPU 必须面对的的一个问题就是,缓存不统一协定,目前应用最宽泛的就是 MESI 协定,MESI 协定如果没有有效化队列和写缓冲器的话,那么就会性能较差,于是人们引入了写缓冲器和有效化队列,然而引入写缓冲器和有效化队列解决了性能问题,又带来了不确定性,即一个处理器对共享变量所做的更新具体在什么时候可能被其余处理器读取到这一点,缓存一致性协定 (带了写缓冲器和有效化队列的版本) 是不保障的,写缓冲器和有效化队列都可能导致一个处理器在某一时刻读取到共享变量的旧值。JVM 必须答复一个处理器对共享变量的更新在什么时候或者说什么状况才可能被其余处理器所读取,即可见性问题。可见性又衍生出一个新的问题,一个处理器先后更新多个共享变量的状况下,其余处理器是以何种程序读取到这些更新的,即有序性问题。除此之外,Java 作为一个跨平台的语言,必须屏蔽掉不同 CPU 内存模型的差别,就必须定义本人的内存模型,这也就是 JMM,Java memory model,在解决跨平台的时候遇到了一些问题,指令重排、存储子系统重排,这两个问题带来了可见性和有序性问题,这两个问题之外,还有原子性,也就是说咱们心愿这操作不能被打断。

咱们权且将存储子系统重排和指令重排都算做指令重排中,那么就能够得出这样的论断:

存储原子性 + 指令重排 = 内存模型

Java 内存模型形象了跨平台会遇到的问题,并给出标准,并未给出实现,这是 Java 一惯的格调,给出标准,不给出实现,因为 JVM 不止一种,要促成 JVM 世界的凋敝,就不要束缚太紧,遵循一个标准,那么 Java 在不同的虚拟机上跑进去的后果,咱们都能够由标准推导进去。JMM 定义了 final、volatile、synchronized 关键字的行为并确保正确同步。从开发人员的角度来看,Java 内存模型作为一个模型,它从什么的角度来答复以下几个线程平安问题:

  • 原子性问题: 针对实例变量、动态变量 (即共享变量而非局部变量) 的读、写操作,哪些是具备原子性的,哪些可能不具备原子性。
  • 可见性问题: 一个线程对实例变量、动态变量进行的更新在什么状况下可能被其余线程所读取。
  • 有序性问题: 一个线程对多个实例变量、动态变量进行的更新在什么状况下在其余线程看来是能够乱序的。

在原子性方面 , Java 内存模型规定对 long/double 型意外的根本数据类型以及援用类型的共享变量进行读、写都具备原子性。另外,Java 内存模型还特地规定对 volatile 润饰的 long/double 型变量进行读、写操作也具备原子性。换而言之,对援用类型以及简直所有根本数据类型进行的读、写操作,Java 内存模型都保障它们具备原子性,而对 long/double 型变量的共享变量进行的读、写操作是否具备原子性则取决于具体的 Java 虚拟机实现。

JMM 的一些行为难以测试,比方指令重排,原子性读取等等,那咱们该如何验证咱们的猜测呢,这个问题的答案叫 JCStress。

JMM 测试利器 -JCStress

历史起源

Circa 2013 (≈ JDK 8),we suddenly realized there are no regular concurrency tests that ask hard questions about JMM conformance

大概在 2013 年,也就是 JDK8 公布的时候,咱们忽然意识到没有惯例的并发测试能够针对 JMM 的一致性进行深刻探索。

Attempts to contribute JMM tests to JCK were futile: probabilistic tests

尝试将 JMM 测试奉献给 JCK 是徒劳的:这是概率性测试。

JVMs are notoriously awkward to test: many platforms, many compilers, many compilation and runtime modes, dependence on runtime profile

JVM 是出了名的难以测试,波及多个平台,多个编译器、多种编译和运行时模式,以及依赖的配置文件,所以测试起来比拟麻烦。

我对下面的话深表同意,我在写多线程相干的测试,往往要加上可能在你的机器上跑不进去,倡议多跑几轮,这让我经常对我的了解有一点不确定,咱们将用这个框架验证 JMM 的行为。咱们齐全没有在一篇文章外面再介绍一下 JCStress,想起了费马的名言:

对于此,我确信我发现一种美好的证法,惋惜这里的空白处太小,写不下。

写在最初

这篇文章大抵前前后后构思了一个月,甚至更久了,最后是想从 MESI 协定讲起,再联合到 JCStress,然而这条线始终顺不下来了,我的脑子跑入了很多概念,操作系统啊,计算机组成原理啊,编译啊,我有的时候会感觉大学的课程,像是我看过的一部武学小说《昆仑》,外面有一个高手将本人的文治拆成五个局部,传授给师傅们,最初这五个局部交融在一起的时候,反而存在些排挤反馈。我又想起看过的一部小说的一部修行功法《梵圣真魔功》,这部功法本来也是被拆分为三部,最初合三为一才见其功。这篇文章在构建的过程就是尝试在交融大学学习的过程,交融本人的了解,当你开始摸索一些问题,会发现自己的一些了解存在一些偏差,但当你感觉你对一个问题有些清晰的时候,仿佛又碰到了一些新的问题,想起杨万里的《过松源晨炊漆公店》:

莫言下岭便无难,赚得行人错喜爱。

政入万山围子里,一山放出一山拦。

在刚写这篇文章的时候,我开始读 Java 内存模型这个词,我发现我尽管意识模型这两个字,想起小学的认词, 其实有些词未必懂,然而会应用,许多时候面临困惑时,能够通过廓清语言和概念来寻找解决方案,咱们是在用语言进行思考。想起了维特根斯坦,在他看来,哲学的外围目标在于廓清概念,因为许多生存 / 工作中的抵触,基本上来看,是每个人对探讨的概念都有一套本人的解释,而鲜少有人考查透彻概念到底是什么。而了解语言和事实的同构关系,除了实际之外,也须要语言自身的准确,否则会有含糊的关联。只管咱们能纯熟应用某个词,但并代表他能解释这个词是什么。究其基本,在搞明确某个概念是什么之前,咱们就先学会了怎么应用这个概念。

参考资料

[1]《计算机体系结构根底》https://foxsen.github.io/archbase/sec-ISA.html#%E6%8C%87%E4%B…

[2] 计算机系统内的字长到底指的是什么?https://www.zhihu.com/question/20536161

[3] RISC VS CISC https://cs.stanford.edu/people/eroberts/courses/soco/projects…

[4] 为什么有的中央叫 arm64,有的中央叫 aarch64?https://www.zhihu.com/question/502737415

[5] 都 2021 年了,还把 x86 和 ARM 归为 CISC 和 RISC?https://zhuanlan.zhihu.com/p/426773593

[6] Weak vs. Strong Memory Models https://preshing.com/20120930/weak-vs-strong-memory-models/

[7]《Java 多线程实战指南》黄文海著

[8] CPU memory model https://bajamircea.github.io/coding/cpp/2019/10/25/cpu-memory…

[9] 种群的“S”型增长 https://www.bilibili.com/video/BV128411W7rt/?spm_id_from=333….

[10] 数学模型(第二版)http://www.tjsiam.org/ICTMA/books/%E6%95%B0%E5%AD%A6%E6%A8%A1…

[11]【数学建模大赛罕用模型】差分方程法建设种群增长模型,三节课彻底搞明确 https://www.bilibili.com/video/BV1Z34y1m75i/?spm_id_from=333….

[12] Java 并发编程之 JMM & volatile 详解 https://juejin.cn/post/6916331359258542087

[13] 什么是时钟速度?https://www.intel.cn/content/www/cn/zh/gaming/resources/cpu-c…

[14] CPU 的主频为什么不能有限晋升?其余条件不变的状况下,主频越高速度越快,为什么主频不能有限进步?https://www.zhihu.com/question/561577035

[15] 多核 CPU 和多个 CPU 有何区别?https://www.zhihu.com/question/20998226/answer/705020723

[16] Java 并发编程:如何创立线程?https://www.cnblogs.com/dolphin0520/p/3913517.html

[17] CPU 内存模型和 Java 内存模型以及 Java 内存区域 https://zhuanlan.zhihu.com/p/145902867

[18] CPU memory model https://bajamircea.github.io/coding/cpp/2019/10/25/cpu-memory…

[19] Java 字节码(根底篇)https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E6%B7%B1%…

[20] Java 字节码技术详解 https://zhuanlan.zhihu.com/p/465426047

[21] Java Bytecode DUP https://stackoverflow.com/questions/12438567/java-bytecode-dup

正文完
 0