volatile与JMM模型

JMM模型

Java内存模型(Java Memory Model简称JMM)是一种抽象的概念,并不真实存在,它
描述的是一组规则或规范。
根据JMM设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

JMM模型与JVM模型的不同

JMM与JVM内存区域的划分是不同的概念层次,更恰当说JMM描述的是一组规则,通
过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式,JMM是围绕

原子性,有序性、可见性 展开。JMM与JVM内存区域唯一相似点,都存在共享数据区域和
私有数据区域。
在JMM中 主内存 属于共享数据区域,从某个程度上讲应该包括了 堆和方法区 ,而 工作内存 数据线程私有数据区域,从某个程度上讲则应该包括 程序计数器、虚拟机栈以及本地方法栈
从更低层次上说,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

数据同步的原子操作

lock(锁定):作用于主内存的变量,一个变量在同一时间只能一个线程锁定。该操作表示该线程独占锁定的变量。
unlock(解锁):作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定。
read(读取):作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用。
load(载入):作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)。
use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作。
assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作。
store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用。
write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

根据原子操作我们简单画个图,运行a++这个操作;

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
1.如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
2.不允许read和load、store和write操作之一单独出现
3.不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
4.不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
5.一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或 assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
6.一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
7.如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需 要重新执行load或assign操作初始化变量的值
8.如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一 个被其他线程锁定的变量。
9.对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

volatile特性

可见性:volatile保证了及时可见性,在一个线程对volatile变量进行了修改,那么其他使用该volatile变量会立刻发现,并丢弃当前volatile变量从主内存重新取。(注意不使用volatile也不代表另一个线程看不到值的修改,一般需要一段时间才能看到,而volatile保证了及时可见)

原子性:volatile对单个volatile变量进行读写操作具有原子性,但对于如volatile++这种复合操作不具有原子性,也因此volatile不具备原子性。

有序性:volatile会通过加内存屏障的方式来禁止指令重排序来保证有序性。

为什么volatile无法保证原子性:volatile++操作并不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分两步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,将会导致并发安全的问题。(之后mesi缓存一致性协议会着重写这方面)

volatile禁止重排序

volatile保证有序性就是依靠禁止指令重排优化,而volatile实现禁止指令重排序优化是依靠内存屏障。

内存屏障

内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条内存屏障则会告诉 编译器和CPU,不管什么指令都不能和这条内存屏障指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

JVM提供了四类内存屏障

LoadLoad屏障
对于这样的语句Load1;LoadLoad;Load2

在Load2及以后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕

StoreStore屏障
对于这样的语句Store1;StoreStore;Store2

在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见

LoadStore屏障
对于这样的语句Load1;LoadStore;Store2

在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕

StoreLoad全能屏障
对于这样的语句Store1;StoreLoad;Load2

在load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见

重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义, JMM会分别限制这两种类型的重排序类型。 下图是JMM针对编译器制定的volatile重排序规则表。

是否能重排序第二个操作
第一个操作普通读/写volatile 读volatile 写
普通读/写可以重排可以重排不可以重排
volatile 读不可以重排不可以重排不可以重排
volatile 写可以重排不可以重排不可以重排

下面是基于保守策略的JMM内存屏障插入策略。

在每个volatile写操作的前面插入一个StoreStore屏障。

在每个volatile写操作的后面插入一个StoreLoad屏障。

在每个volatile读操作的后面插入一个LoadLoad屏障。

在每个volatile读操作的后面插入一个LoadStore屏障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值