美文网首页
Java volatile解读

Java volatile解读

作者: 小马蛋 | 来源:发表于2019-11-07 13:18 被阅读0次

一、开篇

在上一篇文章《Java 锁》里,我们着重介绍了解决多线程在访问同一共享资源时,并发操作产生的数据不安全的方案——锁。

在对共享资源进行操作时,只允许一个持有锁的线程进行操作,其余线程等待获取锁,读写锁可以做到多个读线程进行读操作,但是写操作依然是互斥的。

对某个变量的写操作互斥加锁,而对变量读操作不加锁,只要保证能读到最新值就ok,那么这个变量应该用什么修饰?

阐述上面问题之前,我们先说说并发环境下的三个重要特性,原子性,可见性,有序性,只有满足了这三个特性,才能确保并发操作的正确性。

1)原子性,一个或多个操作要么全部执行且执行过程不被中断,要么全部不执行(防止复合操作,即一段代码一个线程执行时可以确保原子性,但是多个线程共同执行时,就不能确保原子性了)。
2)可见性,多个线程访问同一共享资源,其中一个线程进行了修改,修改后的值能立即被其它线程获取(修改后的值立即更新到主存中,当其他线程读取时,会到主存中读取)。
3)有序性,程序执行的顺序按照代码的先后顺序执行,因为编译器会进行编译优化,当多线程运行时,会引起很多问题,synchronized、Lock也可以解决有序性问题。
    class Singleton {
        private volatile static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() {
            if (instance == null) { // step 1
                synchronized (Singleton.class) {
                    if (instance == null) // step 2
                        instance = new Singleton(); //step 3
                }
            }
            return instance;
        }
    }

上述代码中是经典的单例模式双重检查例子,假设两个线程同时调用getInstance()方法,线程1执行step 1,发现instance为null,然后同步锁住Singleton类,接着再次判断instance是否为null,仍然是null,执行step 3,开始实例化instance,在实例化的过程中,线程2进入step 1,可能发现instance不为null,但是instance可能还没有完全初始化。
因为在对象初始化的时候,要分三个步骤:

伪代码表示:

1)memory = allocate(); // 1 分配对象的内存空间

2)ctorInstance(memory); // 2 初始化对象

3)instance = memory; // 3 设置instance指向对象的内存空间

但是真正的执行顺序并不一定按照 1 -> 2 -> 3执行,因为 2、3需要依赖1 但是2、3并不互相依赖,有可能是 1 -> 3 -> 2 也就是说,变量instance有可能获得了指向的内存地址,但是实例却并没有完全初始化。所以在使用的时候就会出现问题。

我们可以为instance 添加 volatile关键字,也就是使用了对一个修饰了volatile变量的写 happens-before于任意后续对该变量的读这一原则,对应到上面的初始化过程,step 2 和step 3 都是对instance变量的写操作,所以一定发生在后面对instance的读操作之前。

话归原题,只要保证写操作满足原子性、可见性和有序性,读操作满足可见性和有序性就ok了!明白了吗?

volatile:说了半天不就是我吗?

二、定论

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其它线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其它内存操作一起排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

当类的成员变量和静态成员变量被volatile修饰,就代表了:

1)变量的最新值会被立即更新到主存中,其它线程可以获取最新的修改值(可见性)。
2)禁止指令重排(有序性)。

处理器为了提高处理速度,不会直接和内存进行通讯,而是将主内存数据读取到处理器内部缓存后,进行操作,但是操作完数据以后,不知道什么时候写入内存。

如果对声明了volatile变量进行写操作,JVM会向处理器发送一条Lock前缀指令,会将变量所在缓存行的数据写回到主内存,这一步能够确保当线程对该变量进行写操作后,会立即更新到主内存中。

这个时候其它处理器的缓存还是旧值,所以在多处理器环境下,为了确保各处理器缓存一致,每个处理器会嗅探在总线上传播的数据来检查自己的缓存是否有效,当处理器发现自己的缓存行对应的内存地址与总线上传播的对应数据内存地址不一样,就会将当前处理器的缓存行设置成无效状态,当这个处理器对此数据进行修改操作时(一定包含读操作),会强制从主存中把最新的数据读取到处理器缓存中。这一步确保了其它线程获取声明了volatile的变量都是主存中的最新值。

volatile.png

上面的流程图展示了volatile的原理,从上图也可以看出volatile无法保证原子性。

使用volatile的条件:

  • 对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值。
  • 该变量不会与其它状态变量一起纳入不变性条件中
  • 在访问变量时不需要加锁

三、应用

上一篇提到的AQS,内部维护的state变量就是被volatile所修饰的,在ReentrantLock可重入锁里,state代表的就是持有锁的次数,state为0代表没有线程获取锁。

四、结束

volatile无法保证原子性,但是可以保证可见性和有序性。

相关文章

  • java volatile解读

    在网上搜索了一下volatile,文章一大堆,但是能真正理解volatile的含义的不多,更有甚者,仅仅是作者个人...

  • Java volatile解读

    一、开篇 在上一篇文章《Java 锁》里,我们着重介绍了解决多线程在访问同一共享资源时,并发操作产生的数据不安全的...

  • Java Volatile transient 关键字

    Java Volatile transient 关键字 java关键字volatile Volatile修饰的成员...

  • Volatile理解

    Java Volatile1. volatile 理解2. volatile 不保证原子性3. Volatile ...

  • volatile关键字

    volatile keyword example How to use volatile keyword in java

  • volatile关键字

    java中关键字volatile的作用; volatile vs synchronized的区别 Java并发编...

  • 多线程、并发及线程的基础问题

    一、Java 中能创建 volatile 数组吗? 能,Java 中可以创建 volatile 类型数组,不过只是...

  • Java并发编程系列-volatile

    原创文章,转载请标注出处:《Java并发编程系列-volatile》 一、概述 据说,volatile是java语...

  • volatile关键字小总结

    本文内容:1.volatile语义2.由volatile语义引出JMM3.volatile不能保证原子性的解读4....

  • java关键字-volatile

    前言 java 5之前这个关键字备受争议,java5只有volatile才得以重生 因为volatile和java...

网友评论

      本文标题:Java volatile解读

      本文链接:https://www.haomeiwen.com/subject/tzgmbctx.html