Volatile关键字解读及案例
在上篇文中指出Volatile的轻量、读取拿到的是最新写入值、严格的使用条件、只能保证可见性。本篇中主要加深对其理解和使用demo。
1、Java中的内存模型
在理解之前,要能先了解Java的内存模型。明确在内存模型中有严格的区分:主内存和线程内存。主内存是线程间共享的,比如堆内存。线程内存是指每个线程都会有工作内存,工作内存是不能共享,即其他线程不可以访问的,且对于数据操作只能在工作内存中完成。工作内存中的数据是从主内存中拷贝而来的,线程间数据交换必须通过主内存来完成。内存模型如下图所示:
在上述的内存模型中,会出现脏数据的问题。
线程对主内存数据的修改过程,总结:线程内存对主内存的数据更新不是原子操作。(这一点必须明确,因为这是为什么Volatile不能保证原子性的原因)。可分为以下步骤:
1、从主内存中拷贝到线程的工作内存——读取
2、线程读取后的状态具有不确定性,可能处于阻塞状态或立刻对工作内存中进行修改并写入主内存
2、可见性、原子性、有序性
先来了解Java并发编程中的三个基本的概念:可见性、原子性、有序性
可见性:这里是指,线程工作内存对主内存中的数据进行修改,并将修改的写入到主内存中,其他线程能否及时看到主内存中数据已经有变化。
原子性:对于单个或多个操作,要么都成功,要么都失败。
有序性:在JVM中在满足数据依赖的基础上,编译器、处理器允许指令重排序,即不按照代码由上到下顺序执行。
在Java中的有序性8个原则是:
①程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
②锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
③volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
④传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
⑤线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
⑥线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
⑦线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
⑧对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。
3、volatile关键字
当一个共享变量被声明为Volatile变量后,应当明确改变具备的特性是:
1、一旦有一个线程对共享变量的值修改了并完成对主内存的写入,那么其他缓存该变量的线程都会被迫更新为主内存的值。
2、不能保证原子性。(本质原因是线程内存与主内存之间的数据传递不是原子操作,共有8个原子操作)
示例代码:
/**
* 验证Volatile只能保证可见性,不能确保原子性
*
* @author caiqiang
* @version 2017年4月10日
* @see VolatileTest
* @since
*/
public class VolatileTest {
// Volatile关键字修饰,保证可见性
public volatile int inc = 0;
// 自增,对于自增i++不是原子操作
public synchronized void increase() {
inc++ ;
}
public static void main(String[] args) {
final VolatileTest test = new VolatileTest();
// 该线程循环执行10次
for (int i = 0; i < 10; i++ ) {
new Thread() {
// 线程1000次自增
public void run() {
for (int j = 0; j < 1000; j++ )
test.increase();
};
}.start();
}
while (Thread.activeCount() > 1)
// 保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
从开发者的角度来看,这个结果应该是10000。此实验是验证Volatile是否能保证原子性,如果实验的结果是10000,则Volatile能保证原子性;如果实验结果是小于10000,则Volatile不能保证原子性。实验的结果几乎都是小于10000(在有限的次数内)。
4、volatile的典型应用:
1、状态标记量
volatile boolean flag = false;
//线程1
while(!flag){
doSomething();
}
//线程2
public void setFlag() {
flag = true;
}
该例如果没有volatile会怎样呢?线程1正在执行循环,线程2修改了共享变量flag,但是没有立即写入主内存中,那么会导致线程1还在执行循环。这也就是说,加上volatile关键字会使线程在工作内存的修改会立即写入主内存中。
2、单例模式
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
参考文章:http://mp.weixin.qq.com/s/JY1totcwH0E-nboQ_I01Rw