volatile域

目录

 

一、volatile的可见性、有序性、原子性

volatile保证可见性

volatile保证有序性

volatile不能保证原子性

二、volatile变量的应用场景

场景一:状态标志

场景二: 线程安全的双重检查单例模式

应用场景三-volatile bean

应用场景四-读-写锁策略(分段锁的具体实现方式)

总结


一、volatile的可见性、有序性、原子性

volatile保证可见性

  1. 某个共享变量如果被一个线程给修改了,其他线程能够立即知道这个修改操作
  2. 线程的处理器会一直在总线上嗅探,一旦发现其他处理器对主内中的共享变量进行了修改
  3. 会将自己的工作空间中的缓存置为失效,强制去内存中重新读取这个共享变量

volatile如何实现可见性?

 

CPU2对共享变量的值进行了修改以后,CPU1会立即对其工作空间中的变量值进行更新(原有工作空间中的值失效,重新从内存中读取)

volatile保证有序性

  • 通过禁止指令重排保证了有序性
  • 如果一个变量被声明volatile的话,那么这个变量就不会被进行重排序,也就是说,虚拟机会保证这个变量之前的代码一定会比它先执行,而之后的代码一定会比它晚执行
  • 【关键】通过内存屏障(Memory Barrier)来实现禁止指令重排
  • 是一种CPU指令,也称为内存栅栏或栅栏指令,是一种屏障指令。通过插入内存屏障,屏障之前发布的操作一定先执行,屏障之后的操作一定后执行,禁止了前后的指令的重排序优化

volatile不能保证原子性

  • 即使通过volatile对变量进行声明,但如果对变量执行的是非原子操作,也无法保证线程安全
  • 通常使用volatile修饰一个只有原子性读/写操作的变量,类似于++这种复合操作不具有原子性,不适用volatile。因为++的复合性质:先读取;再++;再赋值

二、volatile变量的应用场景

场景一:状态标志

  • volatile修饰一个指示性的变量,通过可见性保证实时性(短时延,因为能立马给感知到)
  • 指示发生了一个重要的一次性事件,例如完成初始化或请求停机

场景二: 线程安全的双重检查单例模式

volatile通过保证有序性,避免了"无序写入"所有可能带来的问题

class DoubleCheck{
    
    private static volatile DoubleCheck  instance;
    
    private DoubleCheck(){
        
    }
    
    public static DoubleCheck getInstance(){
        if(instance == null){
            /*
            线程A,线程B同时进入下面的代码块,线程B进行等待
             */
            synchronized (DoubleCheck.class){
                if(instance == null){
                    instance = new DoubleCheck();
                }
            }
        }
        return instance;
    }
}

 如果不用volatile变量修饰instance变量,会带来如下的问题

我们以为的new操作

  • 分配一块内存M
  • 在内存M上初始化Singleton对象
  • 然后将M的地址赋值给instance变量

new 操作通过指令重排序后发生

  • 分配一块内存M
  • 将M的地址赋值给instance变量,这时instance变量不为空,但是还没有初始化ThreadSafeDoubleCheck对象,从而导致其他线程拿到一个半成品,无法正确使用。
  • 最后在内存M上初始化ThreadSafeDoubleCheck对象
  • 通过给instance变量加以volatile变量的修饰,从而确保线程的安全。

应用场景三-volatile bean

  • 对象是易变数据的持有者,这些对象必须是线程安全
  • 在 volatile bean模式中,JavaBean的所有数据成员都是 volatile类型
  • 比如ConcurrentHashMap之中,当发生hash冲突时,需要使用拉链法来解决hash冲突,为了保证线程安全,需用到volatile变量的修饰。

 

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

应用场景四-读-写锁策略(分段锁的具体实现方式)

  • 如果读操作远远超过写操作,可以结合锁和 volatile来减少开销
  • 使用synchronized加锁机制保证更新动作是原子的,并使用volatile保证读取结果是可见的
  • 如果更新不频繁的话,该方法可实现更好的性能,因为volatile 读操作的开销通常要优于—个无竞争的锁获取的开销
class SafeCounter2{
    
    private volatile int count;
    
    public int get(){
        return count;
    }
    
    //同一时间只有一个线程可以进入add方法
    public synchronized void add(){
        //这是一个复合操作
        this.count ++;
    }
}

 将其修饰为volatile,在读的时候不进行同步操作,get方法并没有加锁,能保证读取的最新性,在写入方法中add()需要进行同步

总结

  • volatile保证了可见性、有序性,不保证原子性
  • 开销一般情况下小于锁的获取,使用无锁的机制完成线程安全任务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值