JMM-volatile
一:概述
JMM 是Java内存模型( Java Memory Model),简称JMM。它本身只是一个抽象的概念,并不真实存在,它描述的是一种规则或规范,是和多线程相关的一组规范。通过这组规范,定义了程序中对各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。需要每个JVM 的实现都要遵守这样的规范,有了JMM规范的保障,并发程序运行在不同的虚拟机上时,得到的程序结果才是安全可靠可信赖的。如果没有JMM 内存模型来规范,就可能会出现,经过不同 JVM 翻译之后,运行的结果不相同也不正确的情况。
计算机在执行程序时,每条指令都是在CPU中执行的。而执行指令的过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程,跟CPU执行指令的速度比起来要慢的多(硬盘 < 内存 <缓存cache < CPU)。因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。也就是当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时,就可以直接从它的高速缓存中读取数据或向其写入数据了。当运算结束之后,再将高速缓存中的数据刷新到主存当中。
二:JMM模型图
1:read/load是一组;use/assign是一组;store/write是指一组;lock/unlock是一组;每组是不可分割独立存在的
2:线程内存数据变化后会自动触发store/write,将修改后的变量写入主存;
3:主存数据的变化,线程内存是无法感知到的,可以通过volatile关键字修饰主存变量让线程感知到主存数据变化;
三:volatile三大特性
1:保证可见性
1.1:概念
只要有一个线程对共享变量的值做了修改,其它线程都将马上收到通知,立即获取最新的值(实际是所有线程直接操作主内存变量)。
1.2:现象举例
package com.test.thread;
/**
* 线程一种现象
* 线程不结束
*/
public class VolatileTest {
private static boolean flag = false;
//private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
//t线程
new Thread(new Runnable() {
@Override
public void run() {
while(!flag){
}
}
}).start();
Thread.sleep(1000l);//等待一秒,等待线程启动
flag = true;
}
}
1.3:原因分析
初始状态, t线程刚开始从主内存读取了 flag 的值到工作内存。
因为 t 线程要频繁从主内存中读取 flag 的值,JIT 即时编译器在运行一段时间后,会将程序优化,将 flag 的值缓存至自己工作内存中的高速缓存中,减少对主存中 flag 的访问,提高效率
1 秒之后,main 线程修改了 flag 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
1.4:解决方案
我们可以使用 volatile 关键字来修饰 flag,它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。
private static volatile boolean flag = true;
2:不保证原子性
2.1:概念
如上例子中体现的那样,可见性保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见,但是,volatile 修饰的变量并不能保证原子性,因为它只能保证当前线程看到的值为最新值,并不能解决指令的交错问题,比如两个线程分别做 i++ 和 i--,操作,就会出现指令交错的问题;volatile不能够解决该类问题。
为了保证原子性,还是需要乖乖的使用 synchronized 或者 ReentrantLock。
或者是有JUC提供的原子变量,Atomic相关类。
3:保证有序性
3.1:概念
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序;如下一块代码:
static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...;
j = ...;
无论是先给i赋值,还是先给j赋值,都不会影响程序的正序性。这种特性称之为『指令重排』,但是多线程下『指令重排』会影响正确性。
volatile关键字可以保证被修饰的变量不会被进行指令重排。