zoukankan      html  css  js  c++  java
  • volatile关键字解析

    volatile 保证此变量对所有线程的可见性。

    这个可见性是指,当一个线程读取volatile修饰的变量时,永远读取的都是最后一个线程写回主内存的最新值。某个线程在读取数据之后,另一个线程对变量值做了修改,这个线程是不知道的,这就导致当前线程读取的值是过期的,当前线程将过期的数据经过计算写回主内存时,就会出现问题。看下面代码:

    public class VolatileTest {
        public static volatile int race = 0;
    
        /**
         * 每次都对race累加
         */
        public static void increase() {
            race++;
        }
    
        private static int THREAD_COUNT = 20;
    
        public static void main(String[] args) {
            // start 20 thread, every thread invoke increase function 20 times
            for (int tCount = 0; tCount < THREAD_COUNT; tCount++) {
                new Thread(() -> {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                    System.out.println(Thread.currentThread().getName() + "is finished");
                }).start();
            }
    
            /**
             * 当还存在线程运行时,不结束主线程。
             */
            while (Thread.activeCount() > 1) {
                System.out.println("main thread yield, the active count is " + Thread.activeCount());
                Thread.yield();
            }
            // in theory, the result is 20 * 10000 = 200000
            System.out.println(race);
        }
    }
    
    

    看结果,race变量的值基本上不会是20000。
    javap -verbose 类文件, 查看方法的字节码片段
    给出方法编译的字节码

      public static void increase();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #2                  // Field race:I
             3: iconst_1
             4: iadd
             5: putstatic     #2                  // Field race:I
             8: return
          LineNumberTable:
            line 14: 0
            line 15: 8
    

    字节码还是变成了几个命令行,假如当前线程执行了getstatic时volatile修饰的变量是正确的,当在执行iconst_1和iadd时,其他线程可能将主内存中的race值加大了,当前线程将变量写回主内存时,就会覆盖之前的值,导致race的值一直累加不到20000。
    通过以上分析,发现如果需要保证方法的原子性还是需要使用synchronized或者java.util.concurrent中的原子类。

    volatile 的第二个作用是禁止指令重排优化。

    先看一段代码:

      public class Singleton {
          private Singleton() {}
          private volatile static Singleton instance;
    
          public static Singleton getInstance() {
              if (instance == null) {
                  synchronized (Singleton.class) {
                      if (instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
    
              return instance;
          }
      }
    

    上述代码是基于双锁检测实现的单例模式。对比instance添加volatile修饰符前后汇编代码:
    添加volatile之前,

    添加volatile之后,

    发现添加volatile修饰符后,汇编多生成了一条

    0x0000000002b83350: lock add dword ptr [rsp],0h
    

    add dword ptr [rsp],0h,意思是将双字节的栈指针寄存器加0,这句代码没有问题,关键是其前面的修饰符lock。lock的作用使得本CPU的Cache写入内存,该写入动作也会引起其他CPU或者核无效化,这样就保证了本次值的修改对其他CPU可见了。

    lock是如何禁止指令重排的呢?指令重排是指CPU允许多条执行不按程序规定顺序执行。但如果指令之间如果有依赖,那么指令就不能重排。例如一个指令,给地址A加1,另一个指令为将地址A的数据乘以2,还有另一条指令是将B地址的数据加5。可以看出指令1和指令2之间是有依赖关系的,不能重排,但是对于指令3 将B地址的数据加5,和指令1和指令2没有依赖关系的,所以可以重排,而且重排完成后依然是有序的。当使用lock指令,将计算的数据更新到主内存后,意味着所有之前的操作都已经执行完成,这样便形成了“指令重排无法越过的内存屏障”。

    I am chris, and what about you?
  • 相关阅读:
    mysql 修改时锁定技术
    eclipse配置java虚拟机的方法 转
    Highcharts2.3.2 网页曲线绘制工具 一淘网价格曲线
    Linux Shell常用技巧(目录) by Stephen Liu
    为zend studio添加phpdocumentor插件
    graphviz入门
    性价比超高的北斗小辣椒
    notepad++和graphviz配合使用
    搜狗的三级火箭
    电信版小黄蜂 双模天语E619亮相3G展会
  • 原文地址:https://www.cnblogs.com/arax/p/9291046.html
Copyright © 2011-2022 走看看