首页 > 其他 > 详细

volatile关键字

时间:2021-02-20 15:59:55      阅读:26      评论:0      收藏:0      [点我收藏+]

volatile关键字

  • Java虚拟机提供的最轻量级的同步机制

 

1、保证此变量对所有线程的可见性

  • 使用volatile关键字修饰变量后,当一个线程对该变量进行了修改,其他线程可以立即得知该变量的新值

  • 但是基于volatile变量运算在并发下并不是线程安全的,因为Java里面的运算操作符并非原子操作,举例说明如下

    // 按正常来说,结果应该为500,但是得到的结果总是小于500,因为自增操作不是原子性操作
    public class TestJUC {
    ?
       // volatile变量
       public static volatile int race = 0;
    ?
       // volatile变量自增操作
       public static void increase(){
           race ++;
      }
    ?
       // 线程数量
       private static final int THREADS_COUNT = 10;
    ?
       public static void main(String[] args) {
    ?
           // 线程数组
           Thread[] threads = new Thread[THREADS_COUNT];
    ?
           // 每个线程都调用自增方法
           for (int i = 0; i < THREADS_COUNT; i++) {
               threads[i] = new Thread(new Runnable() {
                   @Override
                   public void run() {
                       for (int j = 0; j < 50; j++) {
                           increase();
                      }
                  }
              });
               threads[i].start();
          }
    ?
           System.out.println("race = " + race);
      }
    }
  • 依据自增操作说明原子性

    • 通过反编译race ++,得到的字节码指令如下

      public static void increase();
         Code:
        Stack = 2, Locals = 0, Args_size = 0
             0:    getstatic    #13;
             3:    iconst_1
             4:    iadd
             5:    putstatic    #13;
             8:    return
         LineNumberTable:
             line 14: 0
             line 15: 8
    • 当getstatic指令取到race的值放到操作栈顶时,volatile关键字保证了race的值在此时是正确的

    • 但是在执行后续指令时,其他线程可能已经改变了race的值

    • 此时操作栈顶的值就变成了过期的数据

    • 所以putstatic指令执行后可能把较小的race值同步回了主内存中,就会出现数据错误问题

  • 在不符合以下两条规则的运算中,我们要通过加锁保证原子性

    • 运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值

    • 变量不需要与其他的状态变量共同参与不变约束

 

2、禁止指令重排序优化

  • 在硬件架构上,指令重排序是值处理器允许将多条指令不按照出现规定的顺序,分开发送给各个相应的电路单元进行处理,但是需保障重新能得到正确的结果

    指令1:A = A + 10;
    指令2:A = A(指令1执行后得到的A) * 2;
    指令3:B = B - 3;
    ?
    指令2依赖于指令1的值,所以指令1和指令2的顺序不能重排
    ?
    但是指令3不存在与其他指令之间的依赖关系,所以指令3的顺序可以被重排到指令1、2之前或中间,只要保证处理器执行后面依赖于A、B值的操作时能获取到正确的A和B的值
  • volatile修饰的变量,变量修改之后,会存在一个内存屏障

    • 重排序时不能把后面的指令重排序到内存屏障之前的位置

 

3、JMM对volatile变量定义的特殊规则

假设T表示一个线程,V和W分别表示两个volatile变量,在进行read、load、use、assign、store、write操作时需要满足以下规则

  • 1、T操作变量V,use的前一个动作必须是load,load的后一个动作必须是use

    • 在工作内存中,每次使用V前都必须先从主内存中刷新最新的值,用于保证能看见其他线程对变量V所做的修改

  • 2、T操作变量V,store的前一个动作必须是assign,assign的后一个动作必须是store

    • 在工作内存中,每次修改V后都必须立刻同步到主内存中,用于保证其他线程可以看到自己对变量V所作的修改

  • 3、动作A:T操作变量V,use或assign

    动作B:T操作变量V,和A相关联的load活store

    动作C:T操作变量V,和B相关联的read活write

    与此类似:

    动作X:T操作变量W,use或assign

    动作Z:T操作变量W,和X相关联的load活store

    动作Y:T操作变量W,和Y相关联的read活write

    如果A优于W,那么C由于Y

    • 要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序和程序的顺序相同

volatile关键字

原文:https://www.cnblogs.com/LittleSkinny/p/14420083.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!