【Java笔记】volatile 关键字
目录
- 一、代码案例
- 二、问题分析(重点)
- 三、原因(Java层面)
- 四:总结(记住)
一、代码案例
创建两个线程,第一个线程不停的循环去执行自己的任务,第二个线程输入一个停止标识使线程 1 退出
public class Demo07_volatile {// 停止标识static int flag = 0;public static void main(String[] args) {// 创建执行任务的线程Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "线程启动");while (flag == 0) {// 循环处理的任务}System.out.println(Thread.currentThread().getName() + "线程退出");}, "t1");// 启动线程 t1t1.start();// 创建线程 t2Thread t2 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "线程启动");Scanner scanner = new Scanner(System.in);System.out.println("请输入一个非 0 的整数");flag = scanner.nextInt();System.out.println(Thread.currentThread().getName() + "线程退出");}, "t2");// 启动线程 t2t2.start();}
}
正常来说,如果输入了一个非0整数,线程1会停止,但是事实并非如此,这个代码执行的结果是:
线程 t1 并没有退出。
二、问题分析(重点)
见下图,线程 t1 将主内存中的 flag 值读取到自己的工作内存(寄存器)中,此时,另外一个线程 t2 也将主内存中的 flag 值读取到自己的工作内存中,但是 t2 将 flag 值修改为了 1 ,重新写回了主内存中,但是对于线程 t1 来说,只是比较 flag 这个变量的值,从来没有修改过,所以 CPU 认为这个值永远不会改变,也不会从主内存重新读取值。这就出现了多线程环境下,一个线程修改了变量而另外一个线程无法感知到的情况,此时程序就会出现bug。因此,想要解决这个问题,最重要的就是当一个线程修改了另一个线程需要的变量,必须要让另一个线程感知到,也就是解决内存可见性问题。
但在 flag 变量前添加 volatile 关键字之后,线程 t2 修改 flag 值之后,线程 t1 关闭。
三、原因(Java层面)
加了 volatile 关键字的变量,前后都会加内存屏障,涉及到的读和写都是从主内存中获取,最终解决了内存可见性的问题。内存屏障的作用是保证指令执行的先后顺序,从而保证内存可见性。
volatile 写:
volatile 读:
四:总结(记住)
volatile 关键字从真正意义上解决了内存可见性的问题,用 volatile 修饰的变量,由于前后都有内存屏障,保证了指令的执行顺序,与 synchronized 不同,synchronized是通过原子性进而保证了内存可见性,也可以通过禁止指令重排序的方式解决有序性的问题,但是 volatile 不保证原子性。
一句话,volatile 解决了内存可见性,解决了有序性,不保证原子性
多个线程之间涉及的共享变量,如过存在修改的逻辑,只管加 volatile !!!