java内存模型JMM
# 什么是JMM模型
JMM是一组抽象的概念,并不真实存在,他描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段 和构成数组对象的元素)的访问方式。JMM是围绕原子性,有序性、可见性展开的
工作内存和主内存交互图(1-1)
# 主内存
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例 对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常 量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发生线程安全问题。
# 工作内存
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝), 每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安 全问题。
# 主内存和工作内存是如何交互的(了解)
首先Java内存模型定义了以下八种操作,来进行主内存和工作内存交互
(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后
的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存
中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工
作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内
存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存
中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值
传送到主内存的变量中
这里如果是多线程的话,就会引起并发的问题了
图1-1中所示是 个双核 CPU 系统架构 ,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。每个核都有自己的一级缓存,在有些架构里面还有 个所有 CPU 共享二级缓存。 那么 Java 内存模型里面的工作内存,就对应这里的 Ll 或者 L2 存或者 CPU 寄存器。当一个线程操作共享变量时,它首先从主内存复制共享变量到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量值更新到主内存。
那么假如线程A和线程B同时处理一个共享变量,会出现什么情况?我们使用图1-1所示CPU架构,假设线程A和线程B使用不同CPU执行,并且当前两级 Cache都为空,那么这时候由于 Cache的存在,将会导致内存不可见问题,具体看下面的分析
线程A首先获取共享变量X的值,由于两级 Cache都没有命中,所以加载主内存中X的值,假如为0.然后把X=0的值缓存到两级缓存,线程A修改X的值为1,然后将其写入两级 Cache,并且刷新到主内存。
线程A操作完毕后,线程A所在的CPU的两级 Cache内和主内存里面的X的值都是线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了所以返回X=1;到这里一切都是正常的,因为这时候主内存中也是X=1.然后线程B修改X的值为2,并将其存放到线程2所在的一级 Cache和共享二级 Cache中最后更新主内存中X的值为2;
到这里一切都是好的。线程A这次又需要修改X的值,获取时一级缓存命中,并且X-=1,到这里问题就出现了,明明线程B已经把X的值修改为了2,为何线程A获取的还是1呢?
2
3
4
5
所以要谈到并发编程的三个特性 可见性,原子性,有序性,解决了这三个问题并发问题也就引刃而解