volatile的使用

作用

  1. 可见性
  2. 有序性

内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中,当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。

底层实现

内存屏障

使用场景

  1. 普通变量赋值可以,但是含复合运算的赋值不行(类似i++),也不能修饰引用类型,不能保证对象内部的属性可见性。

    volatile int a = 10; // 可以
    volatile DeadlockDemo demo = new DeadlockDemo(); // 不可以
    
  2. 状态标志,判断业务是否结束

    public class VolatileDemo {
      private static volatile boolean flag = true;
      public static void main(String[] args) throws InterruptedException {
          new Thread(()->{
              while (flag){
                  System.out.println("flag的值为:"+flag);
              }
          },"t1").start();
          new Thread(()->{
              flag = false;
              System.out.println("另一个线程更改flag为false了");
          },"t2").start();
      }
    
  3. 开销较低的读,写锁策略

	/**
     * 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
     * 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
     */
    public class Counter {
        private volatile int value;

        public int getValue() {
            return value;   //利用volatile保证读取操作的可见性
        }

        public synchronized int increment() {
            return value++; //利用synchronized保证复合操作的原子性
        }
    }
  1. 双端锁的单例模式

    public class SingletonDemo {
        // volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
        private volatile SingletonDemo instance = null;
    
        private SingletonDemo() {
            //构造器必须私有  不然直接new就可以创建
        }
    
        public SingletonDemo getInstance() {
            //第一次判断,假设会有好多线程,如果 SingletonDemo 没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
            //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
            if (instance == null) {
                synchronized (SingletonDemo.class) {
                    //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断 instance 是null,他就实例化了instance,然后他出了锁,
                    //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new SingletonDemo(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                    if (instance == null) {
                        instance = new SingletonDemo();
                    }
                }
            }
            return instance;
        }
    }
    

    扩展:除了双端锁的单例模式,还有一种安全的单例模式就是静态内部类

    //现在比较好的做法就是采用静态内部内的方式实现
    class SingletonDemo2 {
        private SingletonDemo2() {
        }
    
        private static class SingletonDemoHandler {
            private static SingletonDemo2 instance = new SingletonDemo2();
        }
    
        public static SingletonDemo2 getInstance() {
            return SingletonDemoHandler.instance;
        }
    } 
    
  2. 对象的属性修改原子类(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)

更新的对象属性必须使用 public volatile 修饰符。

使用目的:以一种线程安全的方式操作非线程安全对象内的某些字段

posted @ 2021-09-27 22:34  沈叶唐  阅读(180)  评论(0编辑  收藏  举报