Volatile关键字、如何实现可见性、单例中怎么用?

4.volatile

volatile自身的特性

  1. 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。volatile修饰变量对可见性的影响所产生的价值远远高于变量本身,线程A写volatile变量后,线程B读该变量,那么所有A执行写操作执行可见的共享变量的值,在B读取volatile变量后也成为对B可见的了。
  2. 有序性:volatile会通过禁止指令重排序来保证有序性
  3. 原子性:对任意单个volatile变量的读/写具有原子性(哪怕它是64位的long或者double),但像volatile++这种复合操作不具有原子性

volatile的内存可见性实现原理

导致内存不可见的原因是 线程的本地缓存和内存之间的值不一致导致的,而volatile的读写实现了 缓存一致性(MESI协议) ,其底层原因是因为volatile变量进行写操作时会多一行Lock前缀的汇编代码,使得:

  1. 当前CPU缓存行的数据写回到系统内存

  2. 并通过总线让其他CPU里缓存了该内存地址的本地缓存无效(I)

  3. 追问:如何通过总线让其他缓存了该内存的CPU本地缓存无效(I)?

    答:每个CPU会通过嗅探总线上的数据来查看本地缓存的数据是否过期,一旦CPU发现本地缓存对应的内存被修改,就会将本地缓存设为 无效(I)状态,此后CPU要再想获取这个数据就必须重新填充本地缓存,彼时会将缓存行标记为 共享(S)状态。

volatile的有序性实现原理

导致有序性问题的原因是 指令重排序,而volatile变量会使编译器再生成字节码时插入内存屏障来禁止指令重排序。

  1. 内存屏障的作用是保证特定操作的执行顺序:
    1. 对于Volatile变量进行写操作时,会在写操作后加上一个store屏障指令,将本地缓存中的共享变量值立刻刷新到内存中,并且不会将store屏障之前的代码排在store屏障之后
    2. 对于Volatile变量进行读操作时,会在读操作前面加上一个load屏障指令,马上读取主内存中的数据,并且不会将load屏障之后的代码排在load屏障之前

单例模式中如何使用Volatile?

追问:工作中哪里用到Volatile了?

答:在多线程下保证单例模式,volatile关键字必不可少,否则即使使用DCL双检锁也会由于指令重排序导致有序性问题,可能引发空指针异常。

手写一个volatile的单例模式

volatile+DCL双检锁可以实现线程安全的单例模式,但是不代表单例是安全的。

追问:那Spring容器的bean是线程安全的吗?

答:Spring容器本身并没有为bean提供线程安全的策略

  1. 默认情况下,bean的Scope是单例的,
    1. 如果单例bean是一个无状态的bean,线程只能对它做查询操作,那这个bean是安全的,例如SpringMVC中的Controller、Service和Dao;
    2. 如果是有状态的bean,那在并发环境下就会导致竞态条件(原子性问题)数据竞争(可见性问题),就不是线程安全的。
  2. 原型bean不会产生竞争,所以是线程安全的。
public class VolatileSingleton {
    /**
     * 私有化构造方法、只会构造一次
     */
    private VolatileSingleton(){
        System.out.println("构造方法");
    }

    private  static volatile VolatileSingleton instance = null;

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

    public static void main(String[] args) {
        // new 30个线程,观察构造方法一共被调用几次
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                VolatileSingleton.getInstance();
            }).start();
        }
        // 输出:构造方法
    }
}
#Java开发##内推##春招##实习##笔试题目##面经##笔经##Java#
全部评论

相关推荐

存一千万就可以进大厂实习
石圪节公社发型师:有存一千万的实力还实习个嘚,直接躺平
点赞 评论 收藏
分享
评论
3
21
分享

创作者周榜

更多
牛客网
牛客企业服务