
JMM
Java Memory Model
介绍
JMM:Java内存模型。(一个约定,协议)
使用场景 / 解决的问题
在不同硬件生产商和不同操作系统下,内存的访问逻辑有一定的差异,导致一段代码在某个系统环境下运行良好,并且线程安全,但是换了系统就出现各种问题。
Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能达到相同的访问效果。
概念:内存划分
JMM规定内存主要划分为主内存和工作内存两种。
此处的主内存和工作内存与JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,若非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域。
从底层来看,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。
概念:内存交互操作
内存交互操作有8中,虚拟机实现必须保证每一个操作都是原子的(原子:即不可再分的)。
原子操作 | 说明 |
---|---|
lock(锁定) | 作用于主内存的变量,把一个变量标识为线程独占状态。 |
unlock(解锁) | 作用于主内存的变量,把一个处于锁定状态的变量释放出来。 |
read(读取) | 作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中。与load一起使用。 |
load(载入) | 作用于工作内存的变量,把 read操作从主存中传输的变量 放入工作内存中。与read一起使用。 |
use(使用) | 作用于工作内存的变量,把工作内存的变量传输给执行引擎。每当虚拟机遇到一个需要使用到变量的值,就要用到这个指定。 |
assign(赋值) | 作用于工作内存的变量,把一个从执行引擎中接受到的值放入工作内存的变量副本中。 |
store(存储) | 作用于主内存中的变量,把一个从工作内存变量的值传输到主内存中。与write一起使用。 |
write(写入) | 作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。与store一起使用。 |
JMM原子操作的一些约定:
- 不允许read和load、store和write操作之一单独出现,即使用了read必须load,使用了store必须write;
- 不允许线程丢弃它最近的assign操作,即工作变量数据改变之后,必须告知内存;
- 不允许一个线程将没有assign的数据从工作内存同步回主内存;
- 一个新的变量必须在内存中诞生,不允许工作内存直接使用一个未被初始化的变量。也就是对变量实施use、store操作之前,必须经过assign和load操作;
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁;
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值;
- 如果一个变量没有被lock,就不能对其进行unlock操作,也不能unlock一个被其他线程锁住的变量;
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存。
概念:一些约定
关于JMM的一些同步的约定
- 线程解锁前,必须立刻把共享变量立刻刷回主存;
- 线程加锁前,必须读取主存中的最新值到工作内存中;
- 加锁和解锁是同一把锁。
Volatile
Volatile是Java虚拟机提供的轻量级同步机制。
介绍
hole…
作用/特征
- 保证可见性
- 不保证原子性
- 禁止指令重排
应用场景
单例模式。
特征介绍
特性1:保证可见性
问题简述:
1 | //出现问题:num赋值为1后,线程并没有停止 |
当num赋值为1,子线程没有停止。因为在当前程序中,子线程运行后,会将num值复制到工作区,后续线程使用num值,只会使用到工作区的num,不使用主线程的num,主线程修改num后,子线程工作区的num仍为原来的值,导致在线程内num一直为0,子线程不会停止。
另外的说法:在子线程运行后,num被read/load到子线程的工作区后,线程的执行引擎使用工作区的num。当主线程将num值修改后,子线程工作区的num仍为原来的值,线程不会停止。
解决方法:
1 | //出现问题:num赋值为1后,线程并没有停止 |
在num前加:volatile。
待完善 volatile可以保证可见性。
特性2:不保证原子性
基本问题
代码:
1 | public class Demo29_Volatile不保证原子性 { |
在当前程序中,num输出结果并不是20000。
如何解决这个问题?
方法1: 在add()方法前添加synchronized,或添加lock机制。
方法2: 将int替换为AtomicInteger原子Integer类。
使用原子类解决这个问题:将int替换为AtomicInteger原子Integer类。
分析add()的字节码
add()里的“num++”,是线程不安全的。从底层看,”num++”不是一个原子性操作。
使用原子类,解决原子性问题
1 | public class Demo29_Volatile不保证原子性_从原子角度解决问题 { |
AtomicInteger
类的底层直接与操作系统挂钩!在内存中操作值。
hole….
特性3:禁止指令重排
介绍
Volatile基于内存屏障,可以避免指令重排。
实现原理
在正常的指令流中,xx会为volatile指令前后添加内存屏障,禁止volatile前后的指令顺序交换。
依赖
内存屏障 作用:
- 保证特定的操作的执行顺序;
- 可以保证某些变量的内存可见性;
依赖于“内存屏障”,volatile实现了禁止指令重排。
其他
指令重排
介绍
计算机不按照写的程序的顺序去执行。
可能触发指令重排
- 编译器优化的重排
- 指令并行可能会重排
- 内存系统重排
一个例子
1 | int x = 1; // 1 |
我们期望的: 1234
实际执行可能的存在: 1324 、 2134等
不可能的存在:4123
查看class文件字节码
javap -c 类名.class
原子性操作
原子,即不可再分。