Java内存模型
Java内存模型(Java Memory Model)是Java虚拟机规范定义的,用于屏蔽因不同程序/硬件/操作系统上内存访问的差异,确保程序运行与设计一致,Java内存模型定义了Java虚拟机在计算机内存中的工作方式,确定了在共享内存系统中,多线程程序读写操作行为的规范,其是JVM中的一部分。
在Java内存模型中,规定所有变量都存储在主内存中,每条线程都有自己的本地内存(也叫做工作内存,是独属于单个线程的内存空间),线程在运行过程中在本地内存中完成变量的操作,并不是每次都和主存交互,如下图所示:
在上图中,当线程2对共享变量2进行修改时,共享变量2的内容如果还未同步到主存或者线程1获取共享变量2时,线程2修改操作未执行,都有可能导致线程1拿到的共享变量2的值与预期不同,进而导致程序异常。那么如何保证共享变量2的在线程1和线程2中的一致性呢?依赖于Java内存模型。
对于共享变量而言,Java内存模型中提出了三点特性:
- 原子性:针对一个或多个操作,要么全部执行,要么全部不执行。执行过程中不会被打断;
- 可见性:当有多个线程访问共享变量时,一个线程对数据的修改可以立即被其他线程感知并获取到新值;
- 有序性:程序的执行顺序和开发者预设的代码顺序一致;
依赖这三种特性,进而保证并发场景下,程序运行的正确性,如下图所示:
那么Java内存模型中又是如何实现这三种特性的呢?主要依赖于限制处理器优化和内存屏障两种方式,涉及到的概念主要包含重排序,顺序一致性,happens-before,as-if-serial等
重排序
在程序执行时,为了提高性能,处理器,编译器都会对程序指令进行重排序操作,在Java源代码转化成最终实际执行的指令序列中,会经历三种重排序:
- 编译器优化重排序:编译器在不改变单线程予以的前提下,可以重新安排语句的执行顺序
- 指令级重排序(处理器):现代处理器采用了指令级并行技术,来将多条指令重叠执行,如果不存在数据以来,助力器可以改变语句对应机器指令的执行顺序
- 内存系统重排序:由于处理器使用缓存,是的加载和存储操作看上去可能是乱序执行的
对于JMM而言,其禁止处理器重排序时通过在生成指令序列的适当位置插入内存屏障指令来实现的。
执行重排序具有以下条件:
- 单线程环境下不能改变程序运行结果
- 存在数据依赖关系的不能进行重排序
顺序一致性
顺序一致性是多线程环境下的理论参考模型,为程序提供可见性保证,顺序一致性具有以下特性:
- 一个线程中的所有操作必须按照程序指定顺序执行
- 所有线程都只能看到单一操作执行顺序,不论是否同步
- 每个操作都必须原子执行且立刻对所有线程可见
happen-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须存在happen-before关系,理论上讲,如果一个操作和另一个操作存在happen-before关系,那么这个操作的执行结果对后者可见且必然在后者前执行,两个操作存在happen-before关系并不代表这两个操作不可重排序,重排序后结果不变即可
as-if-serial
不管怎么重排序,程序的执行结果不能被改变,编译器,运行时和处理器都必须遵循该规则。
Java内存模型实现
前面了解了内存模型是什么以及需要解决的问题是什么?那么在Java中,并发环境下如何保证共享变量的三种特性呢?
使用Java中提供的并发处理关键字即可,各关键字及其对应的特性如下表:
特性 | 关键字 | 备注 |
---|---|---|
原子性 | synchronized,原子变量等 | / |
可见性 | volatile,synchronized,final等 | / |
有序性 | Lock,RetreenLock,CountDownLatch等 | / |
常用CPU概念
术语 英文 描述 内存屏障 Memory Barriers 一组处理器指令,用于实现对内存操作的顺序限制 缓冲行 Cache Line 缓存中可分配的最小单位,处理器填充缓存行时会加载整行,需要多个内存读取周期 原子操作 Atomic Operations 不可中断的一个或一系列操作 缓存命中 Cache Hit 访问地址在高速缓存行读取,则称为缓存命中