据说微信搜寻《Java鱼仔》会变更强哦!本文收录于JavaStarter ,外面有我残缺的Java系列文章,学习或面试都能够看看哦
(一)概述
要理解并发编程,首先就须要理解并发编程的三大个性:可见性、原子性和有序性。
咱们明天要讲的volatile保障了可见性和有序性,然而不保障原子性。接下来会通过几段代码和几张图来强化对volatile的理解。
(二)volatile保障可见性
在讲JMM的时候,咱们写了一段程序,并晓得了两个不同线程之间操作数据是不可见的,即线程B批改了主内存中的共享变量后,线程A是不晓得的。这就是线程之间的不可见性。
public class Test { private static boolean flag=false; public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { System.out.println("waiting"); while (!flag){} System.out.println("in"); } }).start(); Thread.sleep(2000); new Thread(new Runnable() { @Override public void run() { System.out.println("change flag"); flag=true; System.out.println("change success"); } }).start(); }}
这段代码的后果是第二个线程批改flag的值不会被第一个线程见到
当初咱们做个小小的扭转,给flag加上volatile修饰词
private static volatile boolean flag=false;
对volatile原理的了解还是须要借助JMM,咱们拿上来第一段代码的执行流程图:
这是未加volatile时的执行过程,最初会停留在第十步,flag会变成true,然而线程A不晓得。
加上volatile后,当主内存的flag被扭转时,volatile通过cpu的总线嗅探机制,将其余也正在应用该变量的线程的数据生效掉,使得这些线程要从新读取主内存中的值,最初线程A就发现flag的值被扭转了。
(三)Volatile保障有序性
Volatile通过内存屏障禁止指令的重排序,从而保障执行的有序性。具体的内容我在指令重排序和内存屏障的时候讲到了,有趣味的小伙伴能够看一下。
(四)Volatile不保障原子性
首先还是拿出一段代码,这段代码很简略,定义一个count,并且用volatile润饰。接着创立十个线程,每个线程循环1000次count++ :
public class VolatileAtomSample { private static volatile int count=0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 1000; j++) { count++; } } }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); }}
最初无论执行多少次,你会发现最初输入的count绝大多数都是小于10000,景象曾经体现出了volatile不能保障原子性,然而为什么呢?
还是通过一个流程图来示意,当线程A执行完count+1后,将值写回到主内存,这个时候因为volatile的可见性,其余工作内存中的count值会被生效掉从新赋值。
可如果线程B刚好执行到第四步呢,线程B工作内存中的count因为volatile变成了1,assign赋值后的count还是等于1,在这里间接少了一次count++。这就是volatile不能保障原子性的起因。
(五)Volatile的应用场景
通过一个很经典的场景来展现一下volatile的利用,双重校验单例:
public class Singleton { private static Singleton Instance; private Singleton(){}; public static Singleton getInstance(){ if (Instance==null){ synchronized (Singleton.class){ Instance=new Singleton(); } } return Instance; }}
下面这段代码置信大家必定很相熟,单例模式最经典的一段代码,获取实例对象时如果为空就初始化,如果不为空就返回实例,看着没有问题,然而在高并发环境下这段代码是会出问题的。
Instance=new Singleton(); 实例化一个对象时,须要经验三步:
留神,这三步是有可能产生指令重排序的,因而有可能是先申请内存空间,再把对象赋值到内存里,最初实例化对象。第一步->第三步->第二步的形式来执行。
当此时有两个线程A和B同时申请对象的时候,当线程A执行到重排序后的第二步时
线程B执行了if (Instance==null)这行代码,因为此时instance曾经赋值到内存里了,所以会间接return Instance; 然而!这个对象并没有被实例化,因而线程B调用该实例时,就报错了。
这个问题的解决办法就是volatile禁止重排序
private static volatile Singleton Instance;
(六)Volatile可能会导致的问题
凡事都考究一个度,volatile也是。如果一个程序中用了大量的volatile,就有可能会导致总线风暴,所谓总线风暴,就是指当volatile润饰的变量产生扭转时,总线嗅探机制机会将其余内存中的这个变量生效掉,如果volatile很多,有效交互会导致总线带宽达到峰值。因而对volatile的应用也须要适度。