学习内容链接:https://www.cnblogs.com/yanlong300/p/9009687.html
JAVA内存模型定义
CPU有缓存一致性协议及内存屏障。而JAVA是一个跨平台的语言,为了实现一处编码处处执行的功能。JAVA需要设计一个中间层模型来匹配不同操作系统及硬件上的差异。
java内存模型(java memory model): java内存模型指定的是java虚拟内存如何与计算机内存(硬件实体RAM)一起配合。java虚拟机本身就是一个抽象的计算机模型,所以java内存模型更贴切的说是虚拟机内存的使用规范。
JAVA内存模型的介绍
java内存模型主要有以下结构:
-
stack(栈)
特点: 存取速度快、对象生命周期确定、数据大小确定。
存储数据:基本类型变量、对象引用(句柄)
位置:缓存、寄存器、写缓冲区。 -
heap(堆)
特点: 存取速度慢、运行时动态分配大小、对象生命抽周期不确定、垃圾回收。
存储数据:对象
位置:主内存、缓存
理论上stack和heap都存储在计算机的主内存上,但是随着CPU的计算其副本会被加载到缓存及寄存器持有,持有的数据遵从MESI缓存一致性原则。
JAVA内存模型的结构
JMM对共享内存的操作做出了两条规则:
1.线程对共享内存的中的变量的操作都必须在自己的工作内存中进行,不能直接在主内存中进行读写。
2.不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成。
JAVA并发问题的根源
假设线程A和线程B同事访问某个对象的成员变量x。当线程a需要操作变量a,时会将a副本复制到线程A的工作内存中。
当线程a未执行完毕,线程b也要访问变量a
但是线程a与线程b操作的是自己工作空间中的变量副本。 线程a中的副本和线程b中间的副本相符不可见。如果a线程率先完成了任务并写回主存。那么线程b的运算就是在使用后脏数据运算。如果b也写回主存那么线程a的任务就会丢失。
为了保证程序的准确性,我们就需要在并发时添加额外的同步操作。
JAVA并发问题的解决方案
操作过程
我们接着再来关注下变量从主内存读取到工作内存,然后同步回工作内存的细节,这就是主内存与工作内存之间的交互协议。Java内存模型定义了以下8种操作来完成,它们都是原子操作(除了对long和double类型的变量)。
锁定(lock):作用于主内存中的变量,将他标记为一个线程独享变量。
通常意义上的上锁,就是一个线程正在使用时,其他线程必须等待该线程任务完成才能继续执行自己的任务。
解锁(unlock):作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
执行完成后解开锁。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
从主内存 读取到工作内存中。
load(载入):把read操作从主内存中得到的变量值放入工作内存的变量的副本中。
给工作内存中的副本赋值。
use(使用):把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。
程序执行过程中读取该值时调用。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
将运算完成后的新值赋回给工作内存中的变量,相当于修改工作内存中的变量。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
将该值从变量中取出,写入工作内存中。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
将工作内存中的值写回主内存。
解决方案
很简单~加锁。同一时间一个变量只允许一个线程持有一个变量,其他线程需要等待线程使用完后在主内存中解锁才能持有使用。