volatile语义

volatile变量

​ 这是Java提供的一种弱同步机制;volatile变量有2种语义。html

  • volatile变量对全部线程都可见
  • volatile变量禁止指令重排序

volatile变量对全部线程都可见

​ 可见性是指:一条线程改变了变量的值,其余线程都能知道。java

在解释这个规则原理以前,先对内存可见性作必定了解:编程

http://www.javashuo.com/article/p-snvjefhh-hs.html安全

java工做线程的内存称为线程的工做内存,线程之间的内存是独立的(Java栈空间是属于线程的,正是因为内存独立),线程之间经过主内存进行信息交换。多线程

​ 正是因为线程之间内存独立,而数据操做都是在线程的工做内存中进行。那么普通的变量就会出现并发安全问题。在2个线程同时从主内存中加载出来了a=1,而且线程1进行操做a=a+1;线程2进行操做a=a+2;那么最后主内存中的数据中,a究竟是多少呢??并发

​ 这取决于到底线程1仍是线程2最后写入,确定是后写入的值生效,将覆盖前面的值。app

那么volatile变量是如何作到全部线程都能知道最新的值的呢?优化

​ 如你所想;volatile变量在使用前,会进行刷新。线程

在2个线程同时从主内存中加载出来了a=1,而且线程1进行操做a=a+1;线程2进行操做a=a+2;那么最后主内存中的数据中,a究竟是多少呢??code

​ volatile变量的操做过程以下:线程1在执行a= a+1时,先刷新a的值,即从新从主内存中获取a的值;执行完以后,将a的值写入到主内存(马上写入,不会重排序)。线程2在执行a=a+2时,也要先刷新a的值,这时假设a已经被线程1改变,那么线程2在执行以前,就会将a更新为新的值。保证了变量a的操做正确性!

volatile变量禁止指令重排序

​ 普通变量能保证最后的结果如程序代码所描述,但不能保证底层实际的执行顺序如程序所写。

在解释这个规则原理以前,先对指令重排序作必定了解:

http://www.javashuo.com/article/p-snvjefhh-hs.html

指令重排序出现的前提:JIT对字节码进行编译获得汇编代码。因此若是程序运行在纯解释器环境(呵,这确定是不可能的),是不存在重排序的现象的,毕竟都没有编译。

而将字节码编译成汇编,JIT会使用分层机制对须要编译的代码进行优化(也不扯太远),最终的结果是:编译成的汇编代码,跟肉眼可见的Java代码顺序不必定一致。详情见见上文博客;那是由于JIT会根据状况将代码打乱重分配,只要保证最后的结果符合happens-before原则。即不破坏程序的有序性!

​ 而使用volatile修饰的变量,编译器和运行时都会注意这个变量是共享的,所以不会将该变量上的操做与其余操做仪器重排序。

​ 那么是为何呢,怎么才能作到不对它进行重排序呢。答案是采用内存屏障(Memory Barrier或Memory Fence)。对volatile变量的操做,都将使用内存屏障,而重排序时,不能将volatile变量后的操做排序到屏障以前。

内存屏障相关:若是了解内存屏障,推荐

  • 《Java并发编程的艺术》
  • 百度:内存屏障

都是些要记的东西,记住原理便可:重排序时不会打乱顺序。

总结

​ 从volatile的语义咱们知道,volatile并非保证线程安全。而是变量共享!若是只有一个线程修改volatile变量,那么其余线程都能读到正确的值。

​ 假设多线程同时写volatile的变量呢,相似上方的分析(线程1和线程2同时写入a的值,或者更多的线程同时写呢…若是将Java语句拆成字节码,咱们在Java代码中的一行代码,可能获得多行字节码,同理一行字节码可能获得多行汇编…因此可见性不等于原子性),咱们很容易就能获得volatile也是不绝对安全的,他不能保证操做的原子性。因此若是想要真正的并发安全(操做原子性),咱们仍是得使用synchornized。

当且仅当知足如下条件时,才应该使用volatile变量:

  • 对变量的写操做不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
  • 该变量不会与其余状态变量一块儿归入不变性条件中。
  • 在访问变量时不须要加锁。

推荐volatile变量用法:

做为标志位

/**
 * @Author: dhcao
 * @Version: 1.0
 */
public class useVolatile {

    /**
     * flag做为标志位,若是发生改变,即要通知全部线程
     */
    volatile boolean flag = false;
    
    public void add(){
        
        while(!flag){
            System.out.println("还不到改变时候...");
        }
    }
}

​ 这只是一个演示,假设咱们有操做:咱们在商城活动中,记录下100个最早进入的ip地址做为幸运用户(不考虑ip重复)!咱们能够增长一个计数器,当计数器达到100时,将flag修改成true,这样,就算多个线程同时进来,他们依然能够知道,已经有100个用户了。

相关文章
相关标签/搜索