面试官: 有了解过volatile关键字吗 说说看

简介: 面试官: 有了解过volatile关键字吗 说说看

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~ 本篇内容纯理论一点


概念回顾

首先我们回顾一下之前讲的基本概念:


内存可见性

内存可见性,指的是线程之间的可见性,当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值


重排序

为优化程序性能,对原有的指令执行顺序进行优化重新排序。重排序可能发生在多个阶段,比如编译重排序、CPU重排序等。


happens-before

遵循happens-before规则,JVM就能保证指令在多线程之间的顺序性符合执行的预期。


volatile

  • 保证变量的内存可见性
  • 禁止volatile变量与普通变量重排序


那么这个内存可见性过程是怎么样的呢❓之前也有给大家演示过具体代码,这里直接给大家总结一下:


所谓内存可见性, 当一个线程对volatile修饰的变量进行操作时,会立即将本地内存中的共享变量刷新到主内存, 同理,当进行操作时,会立即将本地内存失效,从主内存中读取共享变量的值。


在这一点上,volatile与锁具有相同的内存效果,volatile变量的写和锁的释放具有相同的内存语义,volatile变量的读和锁的获取具有相同的内存语义


禁止重排又是怎么回事呢❓

在JSR-133之前的旧的Java内存模型中,是允许volatile变量与普通变量重排序的。想想看,如果可重排,会发生什么?


我们假设有两个线程A和B,一个被volatile修饰的变量a,一个未被修饰的普通变量b,看下边代码:

volatile a = 1;
int b = 2;
public void writer() {
    a = 1; 
    b = 3;
}
public void reader() {
    if (a == 1) { 
        System.out.println(b);
    }
}
复制代码


线程A执行writer方法,首先将a设置为1,此时B线程操作reader方法,此时判断a=1,然后进行输出b=2,线程A多b操作设置为3,其实最终结果应该b=3才对,所以这里重排可能会导致普通变量读错的情况


为了提供一种比锁更轻量级的线程间的通信机制JSR-133决定增强volatile的内存语义:严格限制编译器和处理器对volatile变量与普通变量的重排序。那么它是怎么禁止的呢❓答案是通过内存屏障,或许你听说过这个概念,下面我们一起看一下~


内存屏障

什么是内存屏障呢?在计算机中,主要分为两种,一种是读屏障(Load Barrier)写屏障(Store Barrier)。内存屏障有两个作用:

  • 阻止屏障两侧的指令重排序
  • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,或者让缓存中相应的数据失效。(这里的缓存指的是cpu的多级缓存如L1,L2)


我们写的代码最终都是要通过编译器的,那么编译器是怎么实现这个过程的呢❓


编译器在生成字节码的时候,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。在Java中,JMM内存屏障插入策略可以保证各平台处理器下程序的volatile内存语义正确,具体策略:

  • 在每个volatile写操作前插入一个屏障;
  • 在每个volatile写操作后插入一个屏障;
  • 在每个volatile读操作后插入一个屏障;
  • 在每个volatile读操作后再插入一个屏障。


volatile与普通变量的重排序规则:

  1. 如果第一个操作是volatile读,那无论第二个操作是什么,都不能重排序;
  2. 如果第二个操作是volatile写,那无论第一个操作是什么,都不能重排序;
  3. 如果第一个操作是volatile写,第二个操作是volatile读,那不能重排序。


那么如果第一个操作是普通变量读,第二个是volatile读,可以重排吗❓

答案: 可以的


volatile使用场景

相信在了解以上概念之后,对它应该有一定的认识了, volatile可以保证内存可见性且禁止重排序, 它跟又具有相同的内存语义,又被称为轻量级锁。volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁可以保证整个临界区代码的执行具有原子性。所以更高级一点。但也不是说volatile就不好,作为轻量级的锁,某些场景下还是非常有用的。


我们以双重锁检查单例模式为例,首先我们看一下普通的单例模式:

class Singleton {
    private static Singleton instance;
     private Singleton() {}
    public Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
复制代码


单线程下你可以这么搞,没毛病,多线程下就不行了,所有我们要加,于是双重锁检查下的实现:

class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
复制代码


这样就真没问题了吗疑问❓我们知道在new的时候,主要做了三件事:

  • 分配内存
  • 变量赋值
  • 初始化对象


这个过程中,可能会导致指令重排,有可能你会说里边加了,上节给大家介绍顺序一致性模型中,我们讲过,在同步模式下临界区内的代码可以发生重排序,所以这里还是有可能发生重排序的,所以最终的这个过程,可能会这样:


线程A执行 分配内存 -> 变量赋值, 线程B执行 判断 instance不为null 开始访问对象,实际上对象还未初始化,所以这时候,我们就要加上volatile

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

这样在多线程环境下,可以保证其安全性


结束语

本节内容可能不像之前那么好理解,比较抽象,所以本文也有不足的地方,大家自己可以多查查一些资料,综合理解, 不要去背概念。本节我们提到了锁的概念,下一节,带大家深入学习一下Java的synchronized与锁 ~

相关文章
|
12天前
|
存储 缓存 Java
大厂面试高频:Volatile 的实现原理 ( 图文详解 )
本文详解Volatile的实现原理(大厂面试高频,建议收藏),涵盖Java内存模型、可见性和有序性,以及Volatile的工作机制和源码案例。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:Volatile 的实现原理 ( 图文详解 )
|
3月前
|
缓存 安全 Java
面试官:说说volatile应用和实现原理?
面试官:说说volatile应用和实现原理?
46 1
|
3月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
4月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
64 6
|
3月前
|
缓存 Java 编译器
一文搞懂volatile面试题
这篇文章是关于Java关键字volatile的详细介绍和分析,volatile是多线程访问共享变量时保证一致性的方案,性能优于synchronized,但不保证操作原子性,需要同步处理。
|
4月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
77 4
|
4月前
|
安全 Java
Java面试题:解释synchronized关键字在Java内存模型中的语义
Java面试题:解释synchronized关键字在Java内存模型中的语义
46 1
|
4月前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
59 1
|
4月前
|
安全 Java API
Java面试题:解释synchronized关键字在Java中的作用,并讨论其使用场景和限制。
Java面试题:解释synchronized关键字在Java中的作用,并讨论其使用场景和限制。
40 0
|
4月前
|
存储 缓存 安全
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
37 0