• volatile


    volatile 的主要作用有两个方面【可见性/顺序性[防止指令重排序]】

    可见性:

    首先熟悉一下JVM的内存工作模型[注意这里的工作模型不是堆/栈/方法区这些],

    线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。对volatile变量的写操作与普通变量的主要区别有两点:1 修改volatile变量时会强制将修改后的值刷新的主内存中。 2 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。通过这两点就可以很好的解决可见性问题。
     
    顺序性[防止指令重排序]:
    volatile之所以能够阻止指令重排,是因为底层JVM里面利用了内存屏障来实现的,内存屏障主要有三点功能:
    1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
    2. 它会强制将对缓存的修改操作立即写入主存;
    3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

    举个栗子:

    public class Singleton {
        public static volatile Singleton instance = null;
        private Singleton() {
        }
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (instance) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    双重校验的写法:第一次判断是否为null是为了拒绝掉当对象不为空的时候剩余的线程。里面加锁是为了当对象为null的时候,此时同时进来两个线程(A和B两个线程),我们要保证只有一个线程才可以初始化对象,所以在这里面加上了锁,这样A拿到了锁进去初始化对象,然后进行返回,B再进去此时发现不为null,那么就不执行初始化的过程。这样就能保证上面的单例模式的正常运行,同时为系统也是节约了许多开销(避免每个线程进来加锁--懒汉式写法)
     
    并发编程中谈及到的无非是可见性、有序性及原子性,为什么volatile不能保障原子性?
    因为 volatile 只能修饰变量,但是有些变量的操作就没办法保障了,比如 i++  ,看JVM变异的指令其实是多条语句的,所以不能保障原子性,只能通过加锁的形式帮助去解决。
     
     
     
  • 相关阅读:
    高并发场景 LVS 安装及keepalived的应用
    使用nginx作为http/https正向代理
    Spring5【七】Spring 整合 MyBatis
    Spring5【六】代理模式及 AOP
    MyBatis 配置模板
    Spring5【五】Bean 的自动装配及注解开发
    Spring5【四】依赖注入(DI)
    Spring5【三】IoC 创建对象的方式及配置说明
    Spring5【一】Spring 简介
    MyBatis【七】缓存
  • 原文地址:https://www.cnblogs.com/junbaba/p/14131412.html
Copyright © 2020-2023  润新知