• java面试-谈谈你对volatile的理解


    一、volatile特性:

    volatile是Java虚拟机提供的轻量级的同步机制。主要有三大特性:

    • 保证可见性
    • 不保证原子性
    • 禁止指令重排序

    1、保证可见性

    1)代码演示

    AAA线程修改变量number的值为60,main线程获取到的number值是0,就一直循环等待。

    原因:int number = 0;number变量之前没有添加volatile关键字,没有可见性。添加volatile关键字,可以解决可见性问题。

    public class VolatileDemo {
        int number = 0;
    
        public void addTo60() {
            this.number = 60;
        }
    
        //volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
        public static void main(String[] args) {
            VolatileDemo volatileDemo = new VolatileDemo();
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " come in");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                volatileDemo.addTo60();
                System.out.println(Thread.currentThread().getName() + " update number value:" + volatileDemo.number);
    
            }, "AAA").start();
    
            //第2个线程是main线程
            while (volatileDemo.number == 0) {
                //main线程就一直等待循环,直到number的值不等于0
    
            }
            System.out.println(Thread.currentThread().getName() + " mission is over, main thread number value:" + volatileDemo.number);
        }
    }  

    2)volatile是如何来保证可见性的呢?

    如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令。

    • 将这个变量所在缓存行的数据写回到系统内存。
    • 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

    2、不保证原子性

    1)代码演示

     volatile修饰number,进行number++操作,每次执行number的返回结果都不一样

    public class VolatileDemo {
        volatile int number = 0;
    
        public void increase() {
            number++;
        }
    
        public static void main(String[] args) {
            VolatileDemo volatileDemo = new VolatileDemo();
            for (int i = 0; i < 20; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        volatileDemo.increase();
                    }
                }).start();
            }
    
            // 默认有 main 线程和 gc 线程
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + " finally number value:" + volatileDemo.number);
        }
    }

    2)volatile为什么不保证原子性?

     number++被拆分成3个指令:

    getfield  从主内存中拿到原始值
    iadd 在线程工作内存中进行加1操作
    putfield 把累加后的值写回主内存
    如果第二个线程在第一个线程读取旧值和写回新值期间读取number的值,
    那么第二个线程就会与第一个线程看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败。

    3)如何解决原子性问题

    • CAS机制:AtomicInteger number = AtomicInteger(0)
    • 锁机制:synchronized、Lock

    3、禁止指令重排序

    volatile的写-读与锁的释放-获取有相同的内存效果。

    volatile写-读的内存语义:

    当写一个volatile变量时,JMM会把线程A对应的本地内存中的共享变量值刷新到主内存。
    当读一个volatile变量时,JMM会把线程B对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

    线程A写一个volatile变量,随后线程B读这个volatile变量,实质上是线程A通过主内存向线程B发送消息
    public class VolatileExample {
        int a = 0;
    
        volatile boolean flag = false;
    
        public void writer() {
            a = 1;
            flag = true;
        }
    
    
        public void reader() {
            if (flag) {
                System.out.println("resultValue:" + a);
            }
    
        }
    }

     

    为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

    volatile写插入内存屏障:

    volatile读插入内存屏障:

     二、你在哪些地方用到过volatile

    1、单例模式(双重检查锁DCL)

    以下代码不一定线程安全,原因是有指令重排序的存在,某个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化
    因为instance = new SingletonDemo();可以分为以下3步完成(伪代码)
    memory = allocate(); //1.分配对象内存空间
    instance(memory); //2.初始化对象
    instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null
    步骤2和步骤3间可能会重排序
    使用volatile禁止指令重排序,对volatile变量的写操作都先行发生于后面对这个变量的读操作
    public class SignletonDemo {
    
        private static SignletonDemo instance;
    
        private SignletonDemo() {
            System.out.println(Thread.currentThread().getName() + " 构造方法SingletonDemo");
        }
    
        public static SignletonDemo getInstance() {
            //第一次检测
            if (instance == null) {
                //同步
                synchronized (SignletonDemo.class) {
                    if (instance == null) {
                        //多线程环境下可能会出现问题的地方
                        instance = new SignletonDemo();
                    }
                }
            }
            return instance;
        }
    }

    附:六种常见的单例模式

    2、读写锁手写缓存

    3、CAS JUC包中大量使用volatile

  • 相关阅读:
    Using the @synchronized Directive
    What Are Threads?
    ios 线程同步
    CAAnimation 动画支撑系统
    UIView 动画 依赖与 CALayer的证据
    动画的定义
    Core Animation1-简介
    繁的是表象,简的是本质
    完全自定义动画
    线程安全与可重入
  • 原文地址:https://www.cnblogs.com/wjh123/p/11094691.html
Copyright © 2020-2023  润新知