Volatile关键字:常用与“保证内存可见性和防止指令重排序”。什么是内存可见性?什么是指令重排序下面都会介绍。
首先来看一段代码
package com.example.juc.test; /** * 文件名:VolitaleTest * 作 者:Miles zhu * 时 间:2019/9/10 20:38 * ------------------------- * 功能和描述: **/ public class VolitaleTest { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); new Thread(threadDemo).start(); while (true) { if (threadDemo.isFlag()) { System.out.println("-------------"); break; } } } } class ThreadDemo implements Runnable { private boolean flag = false; @Override public void run() { try { Thread.sleep(200); flag = true; System.out.println("flag=" + flag); } catch (InterruptedException e) { e.printStackTrace(); } } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } }
输出结果:
是不是和想的不一样?
为什么不一样?
分析:首先程序在执行的时候运行的是main函数,然后里面有运行了一个新的子线程,注意这句话:new ThreadDemo(),这句话执行完成以后该类的变量已经到了主存中(可以理解为堆内存),也就是说这个时候flag=false,是在主存中的。JVM为了提高效率会为每个线程分配一个缓存空间,即每个线程都有自己的内存空间(这其实就是内存可见性问题:即当多个线程操作共享数据时,彼此不可见)。每个线程都会将主存中的数据复制一份到自己的内存空间中。当flag被设置为true的时候sleep了200毫秒,这个时候数据还没有被刷新到主存中,main线程此时读取的flag的值也是false,这个时候main线程读取的变量是自己内存中的变量,也就是刚开始读进来的flag=false。所以if(flag)始终为false。如何解决这个问题?
解决方式一:使用synchronized关键字,代码如下:
while (true) { synchronized (threadDemo){ if (threadDemo.isFlag()) { System.out.println("-------------"); break; } }
输出结果:
但是这种方式很显然是不行的,因为synchronized对于程序而言是在是一个重量级的,也就是说效率非常的底下,当多个线程访问的时候,只能一个一个线程的执行。所以这种解决方式直接抛弃
解决方式二:使用Volatile关键字
private volatile boolean flag = false;
输出结果:
加上volatile关键字以后(底层是在将主存的数据进行实时刷新),就可以理解为操作数据就是在主存中进行的(实际上并不是)
volitale性能是比synchronized的效率要高很多的。volidate修饰以后为什么会性能变的低?因为JVM底层会对指令进行重排序,volidate修饰以后就不能对指令进行重排序了,所以相对而言性能变低了。但是volatile并能替代synchronized,因为volatile仅仅保证了内存的可见性,并没有保证原子性,volatile仅仅是一种轻量级的同步策略。不具备”互斥性”
什么是内存可见性?
也就是指的是当多个线程操作共享数据的时候,线程彼此之间是不可见的。还没明白?那么在换一种方式来说,java程序在执行的时候,总会有一个主线程,加入此时有两个子线程,那么线程自己本身会将主线程中的变量赋值一份到自己的本地缓存中,这个本地缓存其实就是每个线程自己的内存空间,那么由此可见该内存空间在不同的子线程之间是不能共享的,例如子线程A和子线程B,子线程A修改了某个变量flag那么这个flag在子线程B中是不可见的。这就是内存可见性。
什么是指令重排序?
请参考这篇博客,http://swiftlet.net/archives/3321