1.什么是JMM?
JMM是Java Memory Mode(Java内存模型 )的缩写,它内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节
2.JMM结构
Java语言规范中提到过,JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个线程又存在自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
3.可见性讲解
可见性:一个线程对共享变量的修改,能够及时地被其他线程看到。
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
3.1 如何实现可见性
要实现共享变量的可见性,必须保证两点:
- 线程修改后的共享变量值能够及时的从工作内存中刷新到主内存中。
- 其他线程能够及时从主内存中获取共享变量的最新值且刷新到自己的工作内存中
3.2 可见性的实现方式
Java 语言层面支持的可见性实现方式:
- synchronized
- volatile
3.2.1 synchronized实现可见性
为什么synchronized能够实现内存的可见性?
a. 首先synchornized实现了互斥锁,代表了当前方法、代码块只能有一个线程执行,其他线程只能进入等待,只有线程退出后其他线程才能执行。
b. JMM关于synchornized的两条规定:
- 线程解锁前必须将共享变量的最新值刷新到主内存中;
- 线程加锁前,将清空工作内存中的共享变量值,从而使用共享变量时需要从主内存中重新读取最新的值,刷新到工作内存中(加锁和解锁必须是同一把锁)
结合这两部分的特性,再总结一下synchronized实现可见性的过程:
- 获得互斥锁
- 清空工作内存副本
- 从主内存获得最新的共享变量副本
- 执行代码(有可能涉及到共享变量值的修改)
- 将更改后的共享变量值刷新到主内存中
- 释放锁
3.2.2 volatile实现可见性
volatile关键字:
- 能够保证volatile变量的可见性
- 不能保证volatile变量的复合操作的原子性
volatile是如何实现内存可见性的: 通过加入内存屏障和禁止指令重排序优化实现的。
- 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
- 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令 store屏障指令:将改变后的工作内存变量值强制刷新到主内存中 load屏障指令:从主内存中读取变量值的最新值,并且刷新到工作内存中
volatile虽然能够通过屏障指令实现内存的可见性,但是在多线程程序中不能保证原子性。
// 假设有个num变量初始值为5
volatile num = 5;
add(){
num ++;
}
假设:
- 线程A读取了num的值 = 5
- 线程B读取了num的值 = 5
- 线程B先执行了add()方法,并且刷新最新值到主内存中,此时num = 6
- 线程A此时的num的值依然 = 5,此时执行add()方法,num = 6
由上可以看出,执行了两次add(),结果是值只增加了1.
结论:volatile能保证内存可见性,但是却不能保证原子性