volatile语义

volatile变量

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

  • volatile变量对所有线程均可见
  • volatile变量禁止指令重排序

volatile变量对所有线程均可见

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

在解释这个规则原理之前,先对内存可见性做一定了解:

https://www.cnblogs.com/dhcao/p/10982278.html

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

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

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

那么volatile变量是如何做到所有线程都能知道最新的值的呢?

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

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

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

volatile变量禁止指令重排序

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

在解释这个规则原理之前,先对指令重排序做一定了解:

https://www.cnblogs.com/dhcao/p/10982278.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个用户了。

posted @ 2019-09-22 17:15  undifinedException  阅读(185)  评论(0编辑  收藏  举报