什么是JMM以及解决什么问题
JMM最初由JSR-133(Java Memory Model and Thread Specification)文档描述,JMM定义了一组规则或规范,该规范定义了一个线程对共享变量写入时,如何确保对另一个线程是可见的。实际上,JMM提供了合理的禁用缓存以及禁止重排序的方法,所以其核心的价值在于解决可见性和有序性。
Java内存模型:
-
主存:
主要存储Java实例对象,所有线程创建的实例对象都存放在主存中,无论该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括共享的类信息、常量、静态变量。
-
工作内存:
主要存储当前方法的所有本地变量信息(工作内存中存储着主存中的变量副本),每个线程只能访问自己的工作内存,因此不存在线程安全问题。
解决的问题
主要解决代码重排序和缓存可见性问题。
怎么解决的
JMM提供的方案包括大家都很熟悉的volatile、synchronized、final等。
JMM的8个指令
如果要把一个变量从主存复制到工作内存,就要按顺序执行Read和Load操作;如果要把变量从工作内存同步回主存,就要按顺序执行Store和Write操作。
说明:
- 其中Read和Load、Store和Write是必须有序,但是并不需要连续;不能单独出现一个指令。
- 一个变量同一时刻只能被一个线程执行lock,但是同一个线程可以执行多次lock,多次lock,意味着只能执行相同次的unlock才能解锁。
解决有序性
提供内存指令,JVM编译器实现这些指令,禁止特定类型的编译器和CPU重排序。
JMM内存屏障主要有Load和Store两类:
-
Load Barrier(读屏障)
在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主存加载数据。
-
Store Barrier(写屏障)
在写指令之后插入写屏障,能让写入缓存的最新数据写回主存。
实际对Load Barrier和Store Barrier进行组合,用于禁止特定类型的CPU重排序。
-
LoadLoad(LL)屏障
Load1; LoadLoad; Load2;
该示例的含义为:在Load2要读取的数据被访问前,使用LoadLoad屏障保证Load1要读取的数据被读取完毕。
-
StoreStore(SS)屏障
Store1; StoreStore; Store2;
该示例的含义为:在Store2及后续写入操作执行前,使StoreStore屏障保证Store1的写入结果对其他CPU可见。
-
LoadStore(LS)屏障
Load1; LoadStore; Store2;
该示例的含义为:在Store2及后续写入操作执行前,使LoadStore屏障保证Load1要读取的数据被读取完毕。
-
StoreLoad(SL)屏障
Store1; StoreLoad; Load2;
该示例的含义为:在Load2及后续所有读取操作执行前,使StoreLoad屏障保证Store1的写入对所有CPU可见。
volatile是怎么解决问题的
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障。
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。