一、简介
【概述】
JMM(JavaMemoryModel)就是Java内存模型,可以把JMM看作是Java定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从Java源代码到CPU可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
【作用】
定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
【规定】
所有的共享变量都存储于主内存。
变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
线程对变量的所有的操作(读、写)都必须在工作内存中完成,而不能直接读写主内存中的变量。
不同线程之间也不能直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存中转来完成。
【示例图】
二、八种内存操作
锁定(lock):作用于主内存中的变量,将他标记为一个线程独享变量。
解锁(unlock):作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入):把read操作从主内存中得到的变量值放入工作内存的变量的副本中。
use(使用):把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
三、三大特征
JMM三大特征分别是:原子性、可见性、有序性。整个JMM实际上也是围绕着这三个特征建立起来的,并且也是Java并发编程的基础。
1、原子性
【概述】
原子性是指一个操作是不可分割、不可中断的,要么全部执行成功要么全部执行失败。JMM只能保证对基本数据类型的变量的读写操作是原子性的(long和double除外)。
【示例】
int x = 1; // 基本类型赋值操作,必定是原子性操作。 int y = x; // 先读取x变量的值,再进行赋值给y变量,进行了两个操作,不能保证原子性。 x++; // 先读取x变量的值,再进行加1,最后再赋值给x变量,进行了三个操作,不能保证原子性。
【实现方式】
Java提供了synchronized关键字。在synchronized修饰的代码块之间的操作都是原子性的。
2、可见性
【概述】
可见性是指所有线程都能看到共享内存的最新状态。即当一个线程修改了一个共享变量的值时,其他线程能够立即看到该变量的最新值。
【实现方式】
volatile关键字:当一个共享变量被volatile关键字修饰时,这个变量被修改后会立即刷新到主内存,保证其他线程看到的值一定是最新的。
final关键字:final修饰的变量,在构造器中一旦初始化完成,如果没有对象逸出(指对象没有初始化完成就可以被别的线程使用),那么其他线程都就可以看见该变量。
synchronized关键字:线程进入synchronized代码块后,线程会获取到lock,将会清空本地内存,然后从主内存中拷贝共享变量的最新值到本地内存作为副本,执行代码,又将修改后的副本值刷新到主内存中,最后线程执行unlock。
3、有序性
【概述】
有序性是指程序执行的顺序按照代码的先后顺序执行。
【实现方式】
volatile关键字:通过在主存中加入内存屏障来达到禁止指令重排序,来保证有序性。
synchronized关键字:一个变量在同一时刻只能被一个线程lock,并且必须unlock后,其他线程才可以重新lock,使得被synchronized修饰的代码块在多线程之间是串行执行的。