【JVM原理】Java 内存模型

前言

Github:GitHub - yihonglei/jdk-source-code-reading: JDK source code reading(java-concurrent)

一 并发编程模型

Java 线程之间主要有两种通信方式:共享内存和消息传递。

1、共享内存

在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态

进行隐式通信。

2、消息传递

在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。

3、同步

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。

二 Java 线程通信

Java 线程之间的通信由 Java 内存模型(JMM)控制,JMM 决定一个线程对共享变量的

写入何时对另外一个线程可见。从抽象角度来看,JMM 定义了线程和主内存之间的抽象关系:

线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地

内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是

JMM 的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件

和编译器优化。

从线程 A 与 B 之间通信需要经历 2 个必要步骤:

step1:线程 A 将本地更新过的共享内存刷新到主内存中去;

step2:线程 B 去主内存读取线程 A 已经更新过的共享变量;

来个实例说明下线程 A 与 B 之间是如何进行通信的,示意图:

线程 A 和线程 B 有主内存中共享变量 X 的副本。

假设初始时,这三个内存中 X 都为 0,线程 A 执行时,把更新后的 X 值(假设为1)临时

存放在自己的本地内存中,当线程 A 和线程 B 需要通信时,线程A首先会把自己本地内存

中修改后的X值刷新到主内存中,此时主内存中的 X值变为了 1。然后,线程 B 到主内存中

去读取线程 A 更新后的 X 值,此时线程 B 的本地内存的 X 值也变为了 1。线程 A 与线程 B

经过主内存通信,对于程序员来说JMM通过控制主内存与每个线程本地内存之间的交互,

为 Java 程序员提供了内存的可见性保证。

三 Java 内存模型 和 volatile 关键字总结

volatile 实现原理

1、CPU 的 MESI 解决了什么问题?它又带来了什么问题?

MESI 缓存一致性协议,保证了每个缓存中使用的共享变量副本是一致的。

会导致性能下降,当某个 Cache Line 标记为 Invalid 状态,或者当某 Cache Line

当前状态为 Invalid 时写入新的数据。这两个操作都比较耗时,如果 CPU 在这两个过程中

一直等待的话,性能就会存在问题。CPU 通过 Store Buffer 和 Invalidate Queue 组件来降低

这类操作的延时。

2、什么是指令重排(重排序)?指令重排的作用?

指令重排:在执行程序时为了提高性能,程序指令的执行顺序有可能和代码的顺序不一致。

重排分类:

  • 编译器重排序。对于没有依赖关系的语句,编译器重新调整语句的执行顺序。
  • CPU 指令重排序。在指令级别,让没有依赖关系的多条指令并行执行。
  • 内存系统的重排序。CPU 使用缓存和读/写缓冲区,指令执行顺序和写入主内存顺序不完全一致。

指令重排作用:充分利用多核 CPU,多级缓存等进行适当的指令重排序,使程序在保证业务

运行的同时,最大限度提升性能。

3、happens-before 是什么?它解决了什么问题?它有哪些规则?

由于指令重排的特性,为了保证程序在多线程的条件下运行结果能够与单一线程下一致,

引入了 happens-before 规则。happens-before 规则的主要目的是用来确保并发情况下

数据的正确性。指令重排序用于提升性能,happends-before 用于保证数据的正确性。

happens-before 规则:

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作,happens-before 于书写在后面的操作。
  • 锁定规则:一个 unLock 操作,happens-before 于后面对同一个锁的 lock 操作。
  • volatile 变量规则:对一个volatile变量的写操作,happens-before 于后面对这个变量的读操作。
  • 传递规则:如果操作 A happens-before 操作 B,而操作 B happens-before 操作C,则可以得出,操作 A happens-before 操作C
  • 线程启动规则:Thread 对象的 start 方法,happens-before 此线程的每个一个动作。
  • 线程中断规则:对线程 interrupt 方法的调用,happens-before 被中断线程的代码检测到中断事件的发生。
  • 线程终结规则:线程中所有的操作,都 happens-before 线程的终止检测,我们可以通过Thread.join() 方法结束、Thread.isAlive() 的返回值手段,检测到线程已经终止执行。
  • 对象终结规则:一个对象的初始化完成,happens-before 它的 finalize() 方法的开始。

4、什么是 Java 内存模型,它解决了哪些问题?

Java 内存模型(Java Memory Model),JMM 定义了线程和主内存之间的关系。

线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的

本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

在硬件内存模型中,各种 CPU 架构的实现是不尽相同的,Java 作为跨平台的语言,

为了屏蔽底层硬件的差异,定义了 Java 内存模型(JMM)。JMM 作用于 JVM 和底层

硬件之间,屏蔽了下游不同硬件模型带来的差异,为上游开发者提供了统一的使用接口。

JMM 解决了原子性、可见性、有序性这三方面的问题,称之为 JMM 的三大特性。

原子性:保证一系列操作要么全部完成,要么全部失败。JMM 通过关键字 synchronized

实现原子性。底层通过 monitorenter 和 monitorexit 命令实现。

可见性:共享变量的修改对其他线程可见。JMM 通过 volatile 关键字保证对共享变量的修改,

马上刷新到主内存,其他线程操作共享变量必须重新从主内存获取副本。

有序性:保证程序按照正确的顺序执行。实现禁止指令重排序可以通过 volatile 添加

内存屏障、happens-before 保证顺序性。

5、volatile 关键字有哪些特性?

volatile 是 Java 虚拟机提供的轻量级的同步机制。

volatile 特性:可见性,有序性(禁止指令重排),但是不保证原子性。

volatile 变量不能用作现线程安全的计数器。

6、什么是内存屏障?它解决了什么问题?

对于处理器重排序,JMM 处理器重排序规则会要求 Java 编译器在生成指令序列时,

插入特定类型的内存屏(Memory Barries,Intel 称之为 Memory Fence)指令,

通过内存屏障指令来禁止特定类型的处理器重排序。

7、volatile 如何防止指令重排?

Lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其

后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面,即在执行

到内存屏障这句指令时,在它前面的操作已经全部完成,保证指令不会进行重排序。

8、volatile 如何保证内存可见性?volatile 能保证原子性吗?

volatile 变量进行写操作时,JVM 会向处理器发送一条 Lock# 前缀的指令,将这个变量所在

缓存行的数据写回到系统内存,同时其他 CPU 里缓存该内存地址的数据无效。线程在进行

读操作时,发现本地内存无效,然后从主内存获取最新的数据,从而保证多核 CPU 以及

多线程的数据可见性。

Lock# 前缀多核处理器下的作用:

  • 将当前处理器缓存行的数据写回到系统内存。
  • 这个写回内存的操作会使在其他 CPU 里缓存了该内存地址的数据无效。

volatile 不是原子性的,要保证原子性,需要加锁处理。

9、volatile 主要的使用场景?

需要保证多线程对共享变量的可见性时使用,比如 AQS 的 state 控制共享资源。

10、volatile 和 synchronized 和 ReentrantLock 的区别?

volatile:轻量级同步机制,具有可见性和禁止指令重排序特性,保证不了原子性。

synchronized:JVM 层级的加锁机制,保证原子操作。

ReentrantLock:Java api 层级的锁机制,基于 AQS 控制 volatile 的 state 同步状态,

保证操作的原子性,提供了更加灵活丰富的锁获取和释放机制。

参考

深入理解Java内存模型(一)——基础(深入理解Java内存模型(一)——基础 | 并发编程网 – ifeve.com

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值