volatile关键字

1.volatile 的特性

  • 在不同线程的工作时保证了变量的可见性,即主内存和线程的工作内存同步。(可见性)
  • 禁止指令重排序。(有序性)
  • volatile只针对单次I/O的原子性,例如:a++,这种属于多步操作运算。

2.volatile 的实现原理

2.1可见性原理

  • volatile保证变量的可见性是基于内存屏障(Memory Barrier)来实现的。
    • 内存屏障,在cpu的场景下又称内存栅栏(sfence,loadfence,mfence),前面两个分别是读写屏障,mfence则是用于同步指令执行的。
    • 在多核cpu中,cpu比内存快,一般都有自己的高速缓存(工作内存);在一段代码的执行过程中,如果其中一个cpu修改了变量值,这个值首先会刷新高速缓存中,再刷新到主内存中,此时另外一个cpu内核获取的依然是旧值;而加上volatile之后,线程修修改变量时,其它线程访问时必定是新值。
    • volatile做了什么?
      • 当在共享变量前面加上volatile在转换为汇编语言时,会多出一条lock(cmpxchg)为前缀的指令,当cpu收到指令时,会立即将工作内存的值刷新到共享该变量主内存中,然后通知其它cpu共享变量的地址无效,其它cpu这时会再向主内存拉取新的值到各自的工作内存。
      • MESI协议(缓存一致性协议):在早期的CPU中,是通过在总线加LOCK#锁的方式实现的,但是这种方式开销太大,所以Intel开发了缓存一致性协议,也就是MESI协议。该缓存一致性思路:当CPU写数据时,如果发现操作的变量时共享变量,即其他线程的工作内存也存在该变量,于是会发信号通知其他CPU该变量的内存地址无效。当其他线程需要使用这个变量时,如内存地址失效,那么它们会在主存中重新读取该值。
public class Test {
    volatile boolean running = true;
    void k(){
        System.out.println("k start");
        while (running){
            /*System.out.println("System.out会进行线程同步,但是其他方法就未必同步");*/
        }
        System.out.println("k");
    }
 	public static void main(String[] args) throws InterruptedException{
	   Test test = new Test();
	
	    new Thread(test::k,"t1").start();
	
	    try {
	        TimeUnit.SECONDS.sleep(1);
	    }catch (InterruptedException e){
	        e.printStackTrace();
	    }
	    test.running = false;
    }
}

(可见性测试代码)

2.2有序性

  • 因为cpu在保证最终一致性的标准下,会对指令操作进行优化,即会发生重排序,加上volatile后会禁止指令重排序。
  • 在volatile关键字修饰的内部,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
    • 内存屏障说明:
      • StoreStore 屏障:禁止上面的普通写和下面的 volatile 写重排序。
      • StoreLoad 屏障:防止上面的 volatile 写与下面可能有的 volatile 读/写重排序。
      • LoadLoad 屏障: 禁止下面所有的普通读操作和上面的 volatile 读重排序。
      • LoadStore 屏障:禁止下面所有的普通写操作和上面的 volatile 读重排序。

3.volatile 的应用场景

  • DCL模型(double check block)
class Singleton {
    private volatile static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            syschronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } 
}

这里第一判空没有进入到锁的竞争,所以第一只是增强并发效率,syschronized锁的第一次,再次判空锁的第二次,这里锁的概念留到下一篇幅讲,暂时略过,这里先讲一下对象实例化过程。

  • 对象实例化过程:
  1. 在堆内存申请空间,给与变量初始化值。
  2. 修改初始化值。
  3. 在栈内与堆建立引用指向。

这里如果没加上volatile,当两个线程A,B同时进入syschronized方法进行等待,有可能A在指令重排的过程进行上述1,3的操作,即对象已经不为null了,这时B线程发现构造的对象已经不是null了,则直接返回对象,所以得到的只是堆空间的初始值,加上volatile则禁止了cpu进行指令重排序。

后记

敬请期待后续~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值