这是我参与更文挑战的第24天,活动详情查看: 更文挑战。
说起
Volatile
关键字,很多人那叫一个气,这个不起眼的Java
关键字为难了太多的英雄好汉。初看它时,觉得格外的简单,仔细看它,才发现打扰了。这篇文章带大家由深及浅的分析这个关键字。
一、灵魂拷问
那一年,在某个你想去奋斗的地方,你可曾被问到以下的问题。
volatile
是什么,它与synchronized
的区别?你在什么地方看到过,用到过?- 哪些场景下适合使用
Volatile
? volatile
是为了解决什么问题?它的优缺点是什么?- 谈谈
volatile
底层原理?再说说MESI
吧? - 你知道
happen-before
原则吗?
COMBO
连击,完全招架不住,我是谁?我为什么在这?时候不早了,我是不是该回去休息了。
二、实例分析
我们先以实例走进这个关键字。
/**
* thread safely
*/
//public static volatile boolean run = true;
/**
* thread not safely
*/
public static boolean run = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
int i=0;
while (run){
i++;
Thread.yield();
System.out.println(i+" loading...");
}
}).start();
System.out.println("Main Thread ready to sleep...");
TimeUnit.SECONDS.sleep(10);
System.out.println("Main Thread finish sleep...");
run= false;
}
复制代码
以上的例子演示了使用Volatile
关键字和不使用的区别。
#线程不安全
public static boolean run = true;
复制代码
使用线程不安全的写法,结果表明,会存在相当小的概率让子线程一直处于RUNNABLE
的状态,永远停下来。需要注意的是,真的概率非常的小,我运行了上万次,才能出现一次子线程无法停下来。但也就这一次可能就会引发一次严重的生产事故。
#线程安全
public static volatile boolean run = true;
复制代码
上面的代码是线程安全的,可以从根本上解决死循环的问题。那么为什么呢?
先简单的解释一下,核心问题就是虽然主线程修改了值,子线程并没有感知到主线程修改run
变量为false
。Why,后面看我细细分析。
三、什么是volatile
volatile
关键字用于多线程修改同一变量值的场景,它使线程能安全的访问、操作共享变量。这意味着多个线程能同时使用一个方法和实例,而不会出任何问题。这个关键字既能修饰Java
的基本类型,也能修改引用类型。
上面的描述都是在说现象,真正的底层原理是,基于JMM( Java Memory Model)
,volatile
关键字用于将 Java
变量标记为“存储在主内存中”。更加准备地说,每一次 volatile
变量的读取都将从计算机的主存
中读取,而不是从CPU
高速缓存中读取,每一次写入 volatile
变量将被写入主存
中,而不仅仅是写入 CPU
缓存中。
众所周知,当多个线程要同时访问共享变量的时,需要考虑三个方面,包括原子性、可见性、顺序性。原子性代表当另一个线程对共享数据执行某些操作时,不应该有线程干扰;可见性代表行为对线程共享数据的影响应该可以被其他线程感知到;顺序性代表指令执行顺序应该与源代码中表达的顺序相同。
分析volatile
,它具有:
- 可见性,对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
- 原子性,对任意单个volatile变量的读/写具有原子性。但是对于
i++
这种复合操作不具有原子性。 - 顺序性,
volatile
关键字能禁止指令重排序,所以volatile
能在一定程度上保证有序性.。