zoukankan      html  css  js  c++  java
  • 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个用户了。

  • 相关阅读:
    用goto做异常处理
    零长度数组的妙用
    DTMF三种模式(SIPINFO,RFC2833,INBAND)
    Myeclipse下的struts2.3.8 配置 保证绝对好用
    Linux内核--内核数据类型
    Linux内核:kthread_create(线程)、SLEEP_MILLI_SEC
    3.4.4 数据预留和对齐(skb_reserve, skb_push, skb_put, skb_pull)
    Linux 2.6内核中新的锁机制--RCU
    Linux中SysRq的使用(魔术键)
    CentOS Linux服务器安全设置
  • 原文地址:https://www.cnblogs.com/dhcao/p/11568239.html
Copyright © 2011-2022 走看看