volatile和synchronized的理解

  在java多线程中用的比较多的就是volatile和synchronized了,先来看看volatile。

volatile是用来修饰变量的,volatile的作用:1、可见性

                                                                      2、禁止指令重排

可见性是指当一个线程修改了变量时,其他的线程也能够看到。

指令重排是指对volatile的变量进行读写时插入内存屏障,禁止cpu以及编译期指令重排。

     我们知道,cpu是有缓存的。所以对于一部分数据来讲,cpu会把他们放入cpu缓存,这样下次cpu要用这些数据的时候就不用去主存中取,这样就提高的速度。在多核情况下,多个cpu把同一个变量放入缓存,如果自顾自的操作这个变量,就会产生安全问题,所以在硬件方面,多数cpu实现了缓存一致性协议(MESI)。在缓存一致性的要求下,每个cpu都会去嗅探总线来检查自己缓存中的数据是不是过期了,但是由于cpu以及编译期会对我们的代码进行指令重排,而且cpu也不会因为以为某个变量失效而停下当前的工作取修改变量的状态,因为这将大大降低cpu的效率,也就是说硬件产商实现的MESI协议不是强一致性的,而是弱一致性的,就像是CAP理论的最终一致性。对于MESI这块讲的不是很清楚,大家可以去看看别人的博客。

     在对volatile变量进行写的时候,把缓存中的数据刷新到主存,同时强制使其他cpu的缓存的该变量无效

     当cpu发现自己缓存中的volatile变量无效时,会去主存中读取。

不少人去了解过MESI协议之后,会觉得MESI协议就够用了,为什么还要有volatile呢?我在这中间谈谈自己的看法

 1、 MESI协议是针对多核cpu的,但是在单核cpu下还是会出现缓存不一致的问题

 2、上面说了,cpu层面的MESI协议是弱一致性的,不是强一致性的,所以cpu层次提供了内存屏障以便高层实现强一致性

 还有的人在了解了volatile之后会问:为什么用volatile修饰 int i,i++是不安全的呢?

  这是因为i++操作分为了三步,取i,对i进行加1,写回主存。当cpuA进行到第二步的时候,cpuB开始执行,cpuB一下子执行3

步,主存中的i的值为1。这时cpuA中i的变量确实已经失效了,因为cpuA已经计算出了i+1的值了,这是一个新的值,然后cpuA回写主存,i的值仍然是1。

jdk1.6以后,优化了synchronized,引入了偏向锁和轻量级锁,锁可以升级但是不能降级。正是因为引入这两种锁,所以synchronized消耗的资源比较少

偏向锁:当一个线程访问同步代码块的时候,会在对象有以及栈帧中的锁记录里存储线程id。以后该线程进入和退出同步块时不需要进行CAS操作进行加锁和解锁,只需要简单的测试一个对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,则表示线程获得了锁,如果失败,需要测试一下Mark Word中的偏向锁标志位是否置为1,如果没有设置,使用CAS竞争锁,如果设置为1,尝试使用CAS将对象头的偏向锁指向自己。

偏向锁的撤销:偏向锁是一种等到竞争出现才释放的锁机制。偏向锁的撤销,需要等到全局安全点,即在这个时间点上没有正在执行的字节码。他首先会暂停拥有偏向锁的线程,然后检查拥有偏向锁的线程是否活着,如果线程不处于活动状态,将对象头设置成无锁状态,如果对象活着,对象头的Mark word要么重新偏向其他线程,要么恢复到无所状态。

轻量级锁:和偏向锁类似,不过在竞争的时候,没有获得锁的线程会自旋,自旋后仍然获取不到锁会导致锁升级成重量级锁。

偏向锁:加锁和解锁不需要额外的消耗,如果存在多线程竞争,锁的撤销会带来额外的消耗,只是用于一个线程访问同步代码块

轻量级锁:竞争的线程不会阻塞,但自旋会消耗cpu。适用于追求响应时间

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值