文章目录
并发理论
Java内存模型(JMM)
首先,线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递。Java内存模型是 共享内存 的并发模型,线程之间主要通过读-写共享变量来完成隐式通信
概念
上面讲的 共享内存 模型指的就是 Java内存模型 (简称JMM):
- Java内存模型决定一个线程对共享变量的写入何时对另一个线程可见
- 线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本
图中所示,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量
JVM对Java内存模型的实现
Java内存模型把内存分成了两部分:线程栈区和堆区,下图展示了Java内存模型在JVM中的逻辑视图:
栈区和堆区
JVM中运行的每个线程都拥有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈。随着代码的不断执行,调用栈会不断变化
线程栈:
- 当前方法的所有本地变量信息
- 所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中
堆区:包含了Java应用创建的所有对象信息,不管对象是哪个线程创建的
重排序和数据依赖性
重排序
在执行程序时,为了提高性能,编译器和处理器会对指令做重排序
- 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
内存屏障(Memory Barrier)
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。但是插入一条 内存屏障 告诉编译器和CPU:不管什么指令都不能和这条 内存屏障 重排序
用volatile实现:内存屏障 所做的另外一件事是强制刷出各种 CPU缓存,如一个 写入屏障 将刷出所有之前写入缓存的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本(也就是volatile,保证最新)
数据依赖性
- 如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性
- 编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序
as-if-serial和happens-before规则
as-if-serial
不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义
happens-before
如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中
happens-before规则
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作
- 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作
- volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读
- 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C