Java内存模型
基本概念
1.并发编程模型
线程之间的通信机制有两种:共享内存和消息传递。Java的并发采用的是隐式的共享内存模型。
Java内存模型(JMM)是围绕着在并发过程中如何处理原子性、可见性和有序性来建立的。
- 原子性:基本数据类型的访问读写是具备原子性的(针对long和double有非原子性协定),synchronized块之间的操作也具备原子性。
- 可见性:当一个线程修改了共享变量(主内存中)的值,其他线程能够立即得知这个修改。
- 有序性:线程内表现为串行的语义,从一个线程观察另一个线程,由于指令重排序和工作内存与主内存同步延迟,看到的操作是无序的。
2.内存模型抽象
- 主内存:存储线程之间共享变量,所有存储在堆内存中的实例域、静态域和数组元素在线程之间共享。
- 工作内存:线程私有,存储该线程读写共享变量的副本,它是JMM的抽象概念,并不存在,它涵盖了缓存、写缓冲区、寄存器及其他硬件和编译器的优化。
- JMM抽象示意图:
3.重排序
在执行程序时,编译器和处理器会对指令重排序,重排序分三种类型:
- 编译器优化的重排序
- 指令级并行的重拍序
- 内存系统的重排序
编译器和处理器遵循as-if-serial语义(不管怎么重排序,都不能改变单线程程序的执行结果),不会对存在数据依赖关系的操作重排序。
4.内存屏障指令
JMM把内存屏障指令分为四类:
- LoadLoad
- LoadStore
- StoreLoad
- StoreStore
5.happens-before规则
在JMM中,如果一个操作执行的结果需要对另一个操作可见,这两个操作之间必须存在happens-before关系。
happens-before规则仅仅要求前一个操作的结果对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
同步机制
1.volatile
对一个volatile共享变量的单个读写操作,相当于对普通共享变量的读写操作使用同一个锁来同步。
volatile保证了多线程操作变量的可见性(新值能立即同步到主内存,每次使用前立即从主内存刷新)和有序性(volatile本身就包含了禁止指令重排序的语义),但不能保证原子性(对任意单个volatile变量的读写具有原子性,但对于volatile++这种复合操作不具有原子性)。
2.锁
锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
ReentrantLock的实现依赖于Java同步框架AQS,AQS使用一个整型的volatile变量state来维护同步状态。
锁释放和获取的内存语义的实现使用了volatile变量的读写所具有的内存语义和CAS自带的volatile读写的语义。
- 公平锁:锁获取时先读volatile变量,锁释放时最后写volatile变量。
- 非公平锁:锁获取时先使用CAS更新volatile变量,锁释放时最后写volatile变量。
3.final
final保证了可见性(被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那在其他线程中就能看见final的值)。
参考书籍
《深入理解Java内存模型》、《深入理解Java虚拟机 第二版》
不为当下而战,何以颠覆未来