第12章 Java内存模型与线程

612 阅读5分钟

Java内存模型(Java Memory Model)

  • 并不是真实物理上存在的
  • 定义了:各种变量的访问规则
  • 可以理解为:在特定的协议下,对特定的内存或高速缓存进行读写访问的「过程抽象」
  • 目的是:屏蔽各种硬件和OS的内存访问差异,使所有 Java 程序再各平台下都能达到「一致性的内存访问」

理解变量

这里说的变量的访问规则中的变量,指的是「实例字段」,「静态字段」,「数组对象」

不包括「方法参数」和「局部变量」(线程私有,不需要共享)

硬件的效率与一致性

我们知道,指令重排发生在很多级别,比如CPU指令重拍(乱序执行,Out-Of-Order Execution),即时编译器指令重排序(Instrction Reorder)等等

注意,.class文件编译生成的字节码文件是不会指令重排序的

Java内存模型

内存模型

在特定的协议下,对内存或者缓存读写访问过程的抽象

Java内存模型

image.png

工作内存中保存了主内存中变量在线程中的本地副本

线程不直接访问共享内存,所有对变量的读写操作需要经过工作内存

目的

屏蔽不同硬件和OS的差异,提供统一的内存模型,使Java程序在各个平台下都能达到一致的内存访问效果

手段

定义程序中各种变量的访问规则

理解

这里的JMM和JVM运行时数据区是没有关系的

从硬件和OS的角度来说,主内存对应了Memory,而工作内存对应了Cache和Register

工作内存和主内存的交互

操作

有8个原子的不可再分的操作,分别是

  • lock 把一个变量标记为线程独占的状态

  • unlock 解除一个变量的线程独占状态

  • read 主内存传输到线程的工作内存中

  • load 接受主内存传输过来的变量并放到变量副本中

  • use 工作内存中的变量副本传递给执行引擎

  • assing 把从执行引擎接受的值赋给工作内存的变量副本

  • write 工作内存的变量副本传输到主内存中

  • store 主内存接受传输而来的值并放到主内存的变量中

规则

  • read / loadstore / write 必需同时发生

  • 不能丢弃assign(不能assign了但是不store

  • 不能无理由的store(没发生assign但是store

  • 新变量只能在主内存中诞生(不能没有assign/load就去use/store

  • 一个变量同一时刻只能被同一线程lock,可以lock多次

  • 只有执行相同数量的unlock才能解锁

  • unlock前必须同步回共享内存

  • 不能unlock被其他线程锁定的变量

long和double

我们之前说过,针对变量的操作必须是原子的,但是针对long和double这种64位长的变量来说,允许使用两条指令来完成

happens-before

Java内存模型的等价判断就是 happens-before 规则,

volatile关键字

volatile关键字是最轻量级的同步机制,它的两个作用是:禁止指令重拍和保证变量在不同线程之间的可见性,我们应该注意到,它不能保证对变量操作的原子性

每次 `use` 前都要 `read``load`
`assign` 后要 `store``write`
禁止指令重排序
  • 禁止指令重拍

    表现为Within-Thread As-If-Serial Semantics

    在字节码文件中加入内存屏障,使得该屏障后面的不会被重排序到前面

  • 可见性

    这是通过对上述指令中操作顺序和连续性的约束来实现的,只能保证可见性,不能保证原子性

原子性、可见性、有序性

原子性

基本数据类型的访问都是具有原子性的

synchronized 能保证原子性

可见性

另一个线程能立刻看到当前线程对变量的修改结果

volatilesynchronizedfinal 都能保证可见性

有序性

表现为一个线程内是有序的,线程间是无序的

volatile(禁止指令重拍)、synchronized(同步)能保证有序性

Happens-Before

Happens-Before 先行发生原则用于判断线程是否安全

  • 程序次序规则

程序执行流中,书写在前面的操作先行发生于书写在后面的操作

  • 管程锁定规则

一个unlock操作先行发生于后面的lock操作

  • volatile变量规则

对volatile变量的写先行发生于后面对volatile变量的读

  • 线程启动规则

Thread对象的start()方法先行发生于此线程的每一个动作

  • 线程终止规则

线程中所有操作先行发生于对此线程的终止操作

  • 线程中断规则

对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断发生

  • 对象终结规则

一个对象的初始化完成先行发生于它的finalize()方法的开始

  • 传递性

A -> B, B -> C => A -> C

时间先后顺序与先行发生原则之间没有因果关系

线程

线程的实现

  • 使用内核线程实现

    • 1 :1

    • 一个用户线程对应一个内核进程

    • 线程的切换开销较大

  • 使用用户线程实现

    • 1 :N

    • 多个用户线程对应一个用户进程

    • 内核感受不到用户线程的存在

    • 切换在用户态完成,切换开销小

    • 应用程序自己实现线程切换

  • 用户进程加轻量级进程混合实现

    • M:N

Java线程

HotSpot虚拟机将一个虚拟机线程映射到一个操作系统线程,调度交给操作系统完成(虚拟机栈和本地方法栈也是做到一起的)

《Java虚拟机规范》不规定Java线程的实现方式

Java线程的转换

image.png