JAVA并发编程之volatile变量应用详解

volatile变量应用详解

关于多线程编程,实在有太多的惊喜在等着你去发现,一步一个脚印,记录在学习过程中关于volatile变量的浅显认识。 —— 苏国宇


一、 Volatile关键字概述

首先明确一点,Volatile是JAVA(其他语言如C++也相似)中的一个关键字,一种书写于成员变量之前的修饰符,如static,public一样,他书写于某种类型的变量之前,表征定义的变量具有相关的特性。之所以说明这一点,是为了避免对标题“Volatile变量”的歧义理解,它不是一种具体的变量的类型,而是对某一种具体变量的修饰。如 volatile int a。
明确这一点后,先大致说明一下这个volatile修饰具有怎样的含义:Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。为了深刻理解上面关于volatile的说明,接下来来做具体的说明。

二、 和volatile相关的几个基本概念

为了更好的理解volatile,首先先说明几个相关的几个基本概念:

(1) 内存模型(JAVA内存模型)
我们知道计算机的cpu在执行指令的时候,是离不开往内存里读数据和写数据的,但是我们也知道往内存里读写数据的速度,相比于CPU执行指令的速度是要慢很多的,于是在CPU和内存之间就有了高速缓存Cache。也就是说为了避免读写内内存降低指令执行的速度和CPU的效率,当程序在运行时,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
这里写图片描述

(2) 可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。假如上面的X具有可见性,也就不会具有上面的问题。

(3) 原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行

(4) 有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

三、 细说Volatile

明白了上述的概念后,可以为volatile修饰重新下一个定义:Volatile 变量具有可见性特性,但是不具备原子特性 ,volatile禁止进行指令重排序。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,也就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,而不是读取原来Cache中的值。 下面具体的来看一段代码:

 static boolean flag = true;
    //线程1
    while(flag){
        doSomething();
    }
    //线程2
    stop = false;

(实际上,上述的这种结构也是我们在编程的过程中常用到的一种结构),由一个状态标志值控制一段程序是否执行,当flag未用volatile修饰时,线程1中可能读到一个值为true的flag到自己的Cache中,每次执行循环判断时,都从Cache中读flag 的值进行判断,导致循环永远不会结束,即使线程2执行后将flag改为false,写回内存中,但是线程1不从内存读值,便始终读到的flag都是true,导致无法结束循环,但是如果flag用volatile修饰后,只要线程2修改了flag 的值,并重新写回内存后,线程1在执行时,cache中的flag值将是无效的,需要强制从内存中读值,也就能读到改变后的flag的值,循环也将能够退出。
上述的定义中还值得注的一点是volatile不具有原子性,我们再来看另一段代码

public class Volatile {
    //定义一个由volatile修饰的变量由多个线程共享
    Public volatile static int x= 0;
public static void main(String[] args) {
    //做10次循环,每次循环创建一个线程,对x做100次自加运算
    for(int i=0;i<10;i++)
    {
        //创建一个线程,对x做100次自加运算
    new Thread(()-> {
        for(int j=0;j<100;j++)
        {
            x++;
        }
    }).start();
    }
    while(Thread.activeCount()>1)  //保证前面的线程都执行完
        Thread.yield();
    //显示x的值
    System.out.println(x);
}
}

最终的运算结果是:

这里写图片描述

实际上,每次运行的结果都可能不同,按照前面的分析,加了volatile修饰后即使是多个线程之间,每次对x的值的更改,其他的线程也会马上接收到,那10次循环,每次循环的一个线程对x执行100次自加运算,那结果应该为1000才对,那为什么每次执行的结果还不相同呢?因为volatile不具有原子性,其实此处的自增运算X++,不是一个原子操作,涉及到一次都读操作,和一次写操作,假设一个线程读完x的值,完成一次自加1后,还未执行写操作,就立马执行另一个线程,此线程执行读操作,从内存读值,由于之前的线程并未执行写操作,还未将增加的值写入内存,第二个线程自然也就只读到原来的值,他执行自增操作后,将值写入内存,线程1读操作已经完成,在执行写操作,再将自己自增完的值写入内存,实际上两个线程就只对x的值增加了1,这就和我们的设想有偏差了,也就导致了每次的结果是不确定的,这也就很好的说明了volatie不具有原子性。

这也提醒了我们在使用volatile修饰的变量时,虽然使得变量具有了可见性,但是并不具有原子性,也就是说volatile的安全使用是有限制的。如果同时需要保证可见性和原子性,那就需要使用synchronized关键字,关于synchronized关键字,这里不做多说,但是我们需要知道synchronized是一个内置锁的加锁机制,当一段临界区代码有了这个加锁机制之后,多个线程中某一时刻只允许一个线程在执行这段这段代码,那既然有了synchronized ,为什么还需要Volatile,网上有一个比较好的回答,volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种synchronized关键字更轻量级的同步机制。volatile 操作不会像锁一样造成阻塞,某些时刻,volatile变量同步机制的性能要高于synchronized,使用上也比使用相应的锁简单得多。

四、正确使用 volatile 变量

刚才已经提到只能在有限的一些情形下安全使用 volatile变量。那下面就说一说我自己觉得可以用到volatile变量的两种情况。

(1)状态标记量
也就是举的第一个代码的例子,将 volatile 变量作为状态标志布尔变量使用,用于指示发生了一个重要的一次性事件。
如:

static boolean flag = true;
    //线程1
    while(flag){
        doSomething();
    }
    //线程2
    stop = false;

(2)独立观察
定期 “发布” 观察结果供程序内部使用,就是说假如一个线程一直在接收一个一直在改变的数据写到volatile变量中,另外一个线程隔一段时间将会读取这个volatile变量进行一些操作。

如:假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。
好像我自己当前能理解、用到的也就这两种,其实,我对上面两种情况的理解也就是多个线程对一个共享变量的操作是原子操作的情况下,可以使用volatile变量,这样不会引起线程同步安全问题的,但使得维护线程同步安全的代价降到很低。无需用到synchronized。

五、 volatile的原理和实现机制

通过网上查阅和书籍查阅,简单理解了volatile的原理和实现机制,这是网上对原理解析的一段话:
《深入理解Java虚拟机》中有这样一句话:

观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。 —— 《深入理解Java虚拟机》

lock前缀指令实际上会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

六、总结

关于volatile变量,我自己通过各方面的资料查阅,理解也就差不多如上,总结来说,volatilei变量是一种比 synchronized关键字更轻量级的同步机制。volatile 操作不会像锁一样造成阻塞,volatile修饰的变量具有可见性,但是不具有原子性,volatile禁止进行指令重排序,严格遵循volatile的使用条件的话,某些情况下可以使用 volatile 代替 synchronized 来简化代码。

资料参考:
[1]海子.Java并发编程:volatile关键字解析[J/OL].http://www.importnew.com/18126.html

[2]周志明.深入理解Java虚拟机 [M].第1版.机械工业出版社,2011年6月

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值