Java并发,什么是可见性?为什么会出现”不可见“

本文探讨了Java并发中的可见性问题,解释了由于CPU高速缓存导致的可见性问题,并详细介绍了volatile关键字的作用和实现原理。通过例子展示了volatile如何保证变量的可见性,但无法保证原子性。文章还提出了使用CAS(Compare and Swap)操作解决原子性问题,并提供了一个使用CAS改造的线程安全加法器示例。
摘要由CSDN通过智能技术生成

什么是可见性?为什么会出现”不可见“

我们已经知道

counter.increment();

编译成字节码为

getfield      #2    
iconst_1
iadd
putfield      #2  

        上一篇已经说过,这里的字节码的执行过程是在工作内存中,但是getField和putField这二条指令其实是跟主内存有交互的,这里还是以Counter类的increment方法为例。

  • getField指令会从主存中读取count的值,但是并不是每次都从主存中读,因为CPU高速cache的存在,我们count值有可能会从cache中读,导致读的并不是最新的

  • putField指令会将count新的值写入主内存,但是也不是立即生效,别的CPU的高速cache中的count不会立即更新,CPU会使用缓存一致性协议来做同步,这个对我们是透明的。

        正是因为CPU高速cache的存在,在多核环境中会有可见性的问题。这里额外提一句 ,之所以有高速cache存在,是为提高运行效率,现代CPU的速度比我们的内存快很多,如果每次都锁总线写主存,会导致执行速度下降很多,这是不可以接受的,木桶理论我们都能理解。这里我也画了一张图,来帮助大家理解。

那有没有办法解决可见性带来的问题呢?当然是有的,对于Java,我们可以使用volatile关键字。

volatile

volatile修饰的变量有下面的特性

  • 在写volatile的时候,有monitor release的语义,会刷新各个cpu中该变量的cache,存入最新的值

  • 在读volatile的时候,有monitor acquire的语义,会使当前cpu的cache中该变量的cache失效,从主存中读取最新的值

  • volatile拥有禁止指令重排序的语义

        其中monitor可以理解为锁,moniter release就是释放锁,monitor acquire就是获取锁,这样就是volatile变量的读写都是直接对主存操作的,相当于牺牲一部分性能来换取可见性,这一部分牺牲的性能一般是可以忽略不计的,只需要知道有这么回事就行。

volatile实现原理

        给count加上volatile修饰符后,查看编译后的字节码后会发现,字节码层面唯一的变化是给count添加了ACC_VOLATILE标识flag,在运行时会根据这个flag会自动插入内存屏障,保证volatile可见性语义,内存屏障一共有四种,分别是:

  • LoadLoad

  • LoadStore

  • StoreStore

  • StoreLoad

这里给出文档中的一个实例,比较形象的说明了内存屏障是怎么插入的。

        ​再回到上面的例子,我们给count添加上volat

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值