第12章 Java内存模型与线程
内存模型
高速缓存的使用引入了「缓存一致性」问题。
而内存模型是指在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。不同架构的物理机器可以拥有不一样的内存模型。
程序的乱序执行
处理器为了充分利用运算单元,可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。
Java 内存模型(Java Memory Model,JMM)
目的:屏 蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
Java 内存模型规定:
- 所有变量都存储在主内存(Main Memory)中
- 每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本
- 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据
- 不同的线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
注意:之前看一些资料说 volatile 是直接操作主内存的,本书中特意提到了这一点,应该是不正确的。
根据《Java虚拟机规范》的约定,volatile 变量依然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般。 — 摘自书中注释
内存间交互操作
lock、unlock、read、load、use、assign、store、write
Volatile 的特殊规则
保证此变量对所有线程的可见性
可见性:当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
禁止指令重排序优化🚫
有 volatile 修饰的变量,赋值后多执行了一个
lock addl $0x0,(%esp)
操作,这个操作的作用相当于一个内存屏障 (Memory Barrier 或 Memory Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置), lock 的作用是将本处理器的缓存写入内存,这让 volatile 变量的修改对其他处理器立即可见。不能保证原子性
long 和 double 的非原子性协定
JMM 允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现自行选择是否要保证 64 位数据类型的 load、store、read 和 write 这四个操作的原子性,
原子性、可见性与有序性
1. 原子性
六个原子性操作指令,可以认为基本数据类型的访问、读写都是具备原子性的
2. 可见性
可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。
3. 有序性
如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程, 所有的操作都是无序的。
Java 与线程
线程是比进程更轻量级的调度执行单位,线程是 Java 里面进行处理器资源调度的最基本单位。
线程的实现
内核线程实现:线程由内核来完成线程切换
实际使用的是轻量级进程 LWP,每个 LWP 对应一个内核线程 KLT。
缺点:轻量级进程基于内核线程实现,各种线程操作都需要进行系统调用。而系统调需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。另外,每个轻量级进程都需要有一个内核线程的支持,要消耗一定的内核资源(如内核线程的栈 空间),因此一个系统支持轻量级进程的数量是有限的。
用户线程实现:完全建立在用户空间的线程库上,用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
用户线程加轻量级进程混合实现:一个内核线程 KLT 对应一个轻量级进程LWP,而一个 LWP 对应多个用户线程 UT
Java 线程状态转换
- 本文作者: Kelly Liu
- 本文链接: http://tiantianliu2018.github.io/2020/03/30/《深入理解-Java-虚拟机》第12章阅读笔记/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!