Java内存模型
Java内存模型(JMM)是一种虚拟机规范,用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
JMM在Java1.5版本中被重新修订, 延续至今。
Java多线程间采用共享内存进行通信,由于采用共享内存进行通信,通信过程中面临一系列可见性、原子性、顺序性等问题, JMM就是围绕多线程通信以及相关的一系列特性而建立的模型。JMM定义了一些语法集, 映射到Java语言就是volatile、synchronized等关键字。
多线程通信方式: 1.共享内存 2.消息通知
简而言之, JMM是为了解决Java多线程通信问题而定义的一套规范。
硬件内存结构
现代硬件内存模型与Java内存模型有一些不同,理解内存模型架构以及Java内存模型如何与它协同工作也是非常重要的。
如上图所示, 现代计算机的内存模型可以简单表示为主存、多级缓存、cpu寄存器等。
cpu的执行速度远远大于内存,现代计算机系统都不得不加一些高速缓存作为cpu和内存之间的缓冲:将cpu运算需要用到的数据复制到缓存中,让计算快速运行,当运算结束后,再将缓存结果写入内存中。
cpu寄存器的运算速度高于高速缓存,缓存的运算速度大于内存,因此每个cpu可能会有多个缓存。在某个时刻,有一些主存的数据被读取到高速缓存中,同时会有一些计算结果会被刷回主存。
存在的问题
-
缓存一致性问题
每个处理器有自己的高速缓存器, 多个高速缓存器共享同一块内存。当多个处理器处理的任务涉及到内存中的同一块区域时,就会存在缓存一致性的问题,到底以哪个处理器同步回来的缓存结果为主? -
处理器优化和指令重排
处理器内部的运算单元为了能尽量被充分利用,处理器会对代码进行过优化乱序处理,同时Java编译器也会做指令重排。指令重排保证结果和顺序执行的计算结果一致, 并不保证代码的执行顺序和程序输入的顺序一致。
单线程的情况下, 即使存在上述问题, 也不会导致计算结果错误。但是多线程的情况下就会带来不可预料的结果。比如两个线程同时修改变量a的值, a的初始值为0, 线程1对a执行+1操作, 并将结果刷入共享内存,线程2也对a执行+1操作, 线程2将结果刷入共享内存时,并不知道a值已经变为1, 还是将当前计算结果刷入共享内存, 最终a的计算结果就会出现0+1+1=1的错误结果。
并发编程的问题
并发编程为了保证线程安全,需要满足以下三个特性:
- 原子性 一个操作cpu不可以在中途暂停然后再调度,即不被中断操作,要不执行完成,要不不执行
- 可见性 多个线程访问同一个变量时, 一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
- 有序性 程序执行的顺序按照代码的先后顺序执行。
为了保证共享内存的可见性、有序性、原子性,内存模型定义了共享内存系统中多线程程序读写操作行为的规范。
Java内存模型(JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果的一致的机制及规范。
Java内存模型规定了所有的变量都存储在主存中,每个线程还有自己的工作内存, 线程的工作内存保存了该线程中用到的变量在主存中的副本拷贝, 线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间通信需要通过工作内存的主存之间的数据同步进行。
而JMM就作用于工作内存和主存之间数据同步过程。它规定了如何做数据同步以及什么时候做数据同步。
JMM是一种规范, 目的是解决并发编程时多线程通过共享内存进行通信的时候,缓存一致性、指令重排等带来的问题。
Java内存模型的实现
Java 中提供了一系列和并发处理的关键字和包,就是Java内存模型封装了底层的实现后提供给程序员使用的接口。比如synchronized、volatile、final等, 以及concurren 包。
- 原子性 Java中为了保证原子性, 提供了monitorenter和monitorexit字节码指令。
- 可见性 Java中的volatile关键字保证了被修饰的变量在被刷新回主存时,所有线程可见。其次synchronized和final关键字也可以实现可见性。
- 有序性 Java中的synchronized和volatile保证有序性。synchronized关键字保证同一时刻只允许一条线程操作, volatile禁止指令重排。