Volatile
一.现代计算机的内存模型
二.JMM
Java内存模型(JavaMemoryModel):描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量,存储到内存和从内存中读取变量这样的底层细节。
三.volatile的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
什么是内存屏障
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。 因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。
作用
1.阻止屏障两边的指令重排序。
2.写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存。
3.读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据。
内存屏障四大指令
内存屏障,其实也就是4条CPU的屏障指令。
volatile关键字系统底层如何加入内存屏障
volatile关键字影响的是Class 内的Field 的flags :添加了一个ACC_ _VOLATILE,JVM在把字节码生成为机器码的时候,发现操作是volatile 的变量的话,就会根据JMM要求,在相应的位置去插入内存屏障指令。
volatile的三大特性
1.可见性
使用volatile修饰共享变量,被volatile修改的变量有以下特点:
1.线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存。
2.线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存。
2.无原子性
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改共享变量的场景必须使用加锁同步。
以i++为例,不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分3步完成。如果第二个线程在第一个线程读取旧值和写回新值期间(上图所指三步期间)读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败,因此对于add方法必须使用synchronized修饰,以便保证线程安全。
从volatile变量的读写过程分析
read-load-use和assign-store-write成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次。
3.指令禁重排
是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序。不存在数据依赖关系,可以重排序; 存在数据依赖关系,禁止重排序 。但重排后的指令绝对不能改变原有的串行语义。