目录
文章目录
- 目录
* 1、什么是Java内存模型
* 2、原子性
* 3、可见性
* 4、有序性
* 5、JVM内存模型
1、什么是Java内存模型
描述的是一组规则或规范,通过这组规范定义了程序中各个变量的访问方式。
围绕原子性、有序性和可见性展开。
2、原子性
原子性是指操作是不可分的,要么全部一起执行,要么不执行。
实现原子性方法大致有两种:
- 锁机制
- 无锁CAS机制
3、可见性
可见性是值一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。
- 定义的所有变量都储存在 主内存中
- 每个线程都有自己 独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
- 线程对共享变量所有的操作都必须在自己的工作内存中进行,不能直接从主内存中读写(不能越级)
- 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行。(同级不能相互访问)
线程需要修改一个共享变量X,需要先把X从主内存复制一份到线程的工作内存,在自己的工作内存中修改完毕之后,再从工作内存中回写到主内存。如果线程对变量的操作没有刷写回主内存的话,仅仅改变了自己的工作内存的变量的副本,那么对于其他线程来说是不可见的。而如果另一个变量没有读取主内存中的新的值,而是使用旧的值的话,同样的也可以列为不可见。
共享变量可见性的实现原理:
- .线程A在自己的工作内存中修改变量之后,需要将变量的值刷新到主内存中
- 线程B要把主内存中变量的值更新到工作内存中
实现方法:
- volatile
- synchronized
- 锁
4、有序性
有序性指的是程序按照代码的先后顺序执行。
为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的先后顺序,比如程序。
比如单例模式的双重校验锁的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1public class Singleton {
2 private static Singleton instance;
3 public static Singleton getInstance() {
4 if (instance == null){
5 Synchronized(Singleton.class){
6 if (instance == null){
7 instance = new Singleton();
8 }
9 }
10 }
11 return instance;
12 }
13}
14
15
我们来看instance实例过程:
未被编译器优化的操作:
- 指令1:分配一款内存M
- 指令2:在内存M上初始化Singleton对象
- 指令3:将M的地址赋值给instance变量
编译器优化后的操作指令:
- 指令1:分配一块内存S
- 指令2:将M的地址赋值给instance变量
- 指令3:在内存M上初始化Singleton对象
此时如果有两个线程,刚好执行的代码被优化过:
最终线程B获取的instance是没有初始化的,此时去使用instance可能会产生一些意想不到的错误。
所以双重校验锁可以通过添加
volatile关键字来禁止指令重排序。
单例模式双重校验锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1Public class Singleton{
2 Private volatile static Singleton Instance = null;
3 Private Singleton(){
4 }
5 Public static Singleton getInstance(){
6 If(Instance == null){
7 Synchronized(Singleton.class){
8 If(Instance == null){
9 Instance = new Singleton();
10 }
11 }
12 }
13 Return Instance;
14 }
15}
16
17
静态内部类实现:
1
2
3
4
5
6
7
8
9
10
11
12 1 Public class Singleton{
2 Private Singleton(){
3 }
4 Private static class SIngletonHolder{
5 Private static final Singleton Instance = new Singleton();
6 }
7 Public static Singleton getInstance(){
8 Return SingletonHolder.Instance;
9 }
10}
11
12
5、JVM内存模型
线程公有的包括堆、方法区,线程私有的包括虚拟机栈、本地方法栈、程序计数器。
- 程序计数器:程序执行的字节码行号指示器
- 虚拟机栈:每个方法执行都会创建一个栈帧,栈帧有一个局部变量表, 存储着局部变量、对象引用地址、返回地址等信息
- 本地方法栈:作用同上,为 native 方法服务
- 方法区:jdk1.8 之前的元空间,存储类加载信息
- 堆:主要存储对象和数组,分 2/3 新生代(0.8Eden,0.1 存活区*2)、1/3 老年代 l 常量池:移到了堆中,存放静态变量、常量、符号引用