1.场景的引入,问题的出现很多同学在外出面试的时候都会被问到一个常见的问题:说说你对volatile的理解明白吗?很多初出茅庐的同学可能有点措手不及,因为他们之前可能没有注意过这一点。但是如果在网上百度一下,很多文章写的不错,但是理论太深了,文字太多,图片太少,有点看不懂。基于以上痛点,本文尝试站在青年学生的角度,用最简单的白话,配上多张图告诉大家,什么是volatile?当然,本文不会把理论深究得太深,因为一下子说的太多了,很多同学还是会觉得看不懂。这篇文章只是定位于白话讲清楚volatile这个东西,但是涉及到一些特殊的底层原理和技术问题,以后有机会再写。首先给大家放一张图,大家一起来看一下:如上图,这张图说明在java内存模型中,每个线程都有自己的工作内存,同时也有共享的主内存.例如,如果有两个线程,其代码都需要读取数据变量的值,则它们都将数据变量的值从主内存加载到自己的工作内存中,然后它们可以使用该值。好了,现在从图中可以看出,每个线程都加载了一份变量data到自己的workingmemory中,所以每个线程都可以读到valuedata=0。这样,在线程代码的运行过程中,数据值可以直接从工作内存中加载,不需要从主内存中加载。那么问题来了,为什么每个线程都必须使用一个工作内存来存储变量的副本以供读取呢?总不能每次都让线程从主存中加载变量的值吧?这很容易!因为线程运行的代码对应一些指令,是CPU执行的!但是CPU每次执行一个指令操作,也就是执行我们写的大块代码的时候,如果每次都需要一个变量的值,就从主存中加载,性能会比较差!所以后来想到了一个办法,就是线程有工作内存的概念,类似于一个高速的本地缓存。这样线程的代码在执行过程中可以直接从自己的本地缓存中加载变量副本,而不需要从主存中加载变量值,性能可以得到很大的提升!但是想想,有什么问题呢?我们想象一下,如果线程1将data变量的值修改为1,然后将这个修改写入自己的本地工作内存中。那么此时线程1的工作内存中的数据值为1。但是,主存中的数据值仍然为0!线程2工作内存中的数据值还是0?!这就尴尬了,那么,在线程1的代码运行过程中,他可以直接读到data的最新值为1,但是在线程2的代码运行过程中,读到的数据的值仍然是0!结果,线程1和线程2其实都是在操作一个变量data,但是线程1修改了data变量的值后,线程2是看不到的,总是在自己的本地工作内存中看到一个旧变量。副本的价值!这就是java并发编程中所谓的可见性问题:当多个线程并发读写一个共享变量时,有可能某个线程修改了该变量的值,其他线程却看不到!即对其他线程是不可见的!2.volatile的作用及其背后的原理那么我们要解决这个问题应该怎么办呢?这个时候,就轮到volatile登场了!只需要在定义的时候给data变量加上一个volatile,就可以直接完美的解决这个可见性问题。比如下面的代码,加上volatile之后,会有什么效果呢?完整的功能我就不给大家解释了,因为我们的定位是大白话。如果在这里把底层涉及到的各种内存屏障和指令重排的概念拿出来,很多同学又会一头雾水了!让我们在这里谈谈他最重要的角色。首先,一旦数据变量定义了volatile修饰符,只要线程1修改了数据变量的值,它就会在修改了自己本地工作内存中数据变量的值后强制使用最新的数据变量值。要刷新主存,必须立即将主存中数据变量的值更改为最新值!整个过程如下图所示:二、如果此时其他线程的工作内存中有数据变量的本地缓存,即变量的副本,则工作内存中的数据变量其他线程的会被强制缓存立即失效过期,不允许再次读取和使用!整个过程如下图所示:第三,如果线程2在代码运行过程中需要再次读取data变量的值,此时如果尝试从本地工作内存中读取,会发现,data=0已过期!此时,他必须从主存中重新加载数据变量的最新值!然后就可以读到data=1的最新值了!整个过程,见下图:Bingo!好了,volatile完美的解决了java并发中的可见性问题!给一个变量加上volatile关键字后,只要有线程修改了这个变量的值,就会被强制立即flush回主存。然后强制其他线程本地工作内存中的缓存过期,最后其他线程读取变量值时,强制再次从主内存中加载最新的值!这样可以确保如果任何线程修改变量值,其他线程可以立即看到!这就是所谓的volatileguaranteedvisibility的工作原理!3.Summary&Reminder最后跟大家提一下,volatile的主要作用是保证可见性和有序性。排序涉及到指令重排、内存屏障等更复杂的概念,本文没有提及,但volatile不能保证原子性!也就是说,volatile主要是解决了一个线程修改变量值后,其他线程可以立即读取最新值的问题,解决了这个问题,就是可见性!但是,如果多个线程同时修改一个变量的值,多线程并发仍然可能存在安全问题,导致数据值修改无序。volatile不负责解决这个问题,也就是不负责解决原子性问题!原子性问题不得不通过synchronized、ReentrantLock等锁机制来解决。
