YNZH's Blog

synchronized

底层实现原理

synchronized所用的锁存储于Java对象头中,对于同步代码块是使用monitorenter和monitorexit指令完成的,monitorenter是在编译后插入到同步代码前,monitorexit指令插入代码结束处和异常处,对于同步方法则是使用ACC_SYNCHRONIZED标识方法为需要同步的方法。对于每个Java实例对象都会有一个monitor对象,当jVM执行字节码到需要同步的地方时就会判断对象头中synchronized所指向的monitor对象是否上锁,若没有被锁定或者当前线程已经拥有锁该锁的计数器加一(可重入),相应的当jvm执行到monitorexit指令或者退出同步方法的时候计数器减一,直到计数器为0则释放该锁,若已经被锁定则当前线程进入等待或者阻塞,直到锁的释放。
注意:

  • synchronized是可重入锁,意味着同一线程可以递归的获取该锁而不会导致死锁
  • synchronized作用于方法上是不会被继承

Mutex Lock

Monitor锁本质上依赖于操作系统的 Mutex Lock互斥锁来实现的,工作方式如下
1. 申请mutex
2. 如果成功,则持有该mutex
3. 如果失败,则进行spin自旋. spin的过程就是在线等待mutex, 不断发起mutex gets, 直到获得mutex或者达到spin_count限制为止
4. 依据工作模式的不同选择yiled还是sleep
5. 若达到sleep限制或者被主动唤醒或者完成yield, 则重复1)~4)步,直到获得为止

由于Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一条线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。所以synchronized是Java语言中的一个重量级操作,所以在JDK1.6中加入了对synchrinized的优化

synchronized优化

JDK1.6以后引入了偏向锁、轻量级锁、重量级锁以及锁消除、锁粗话等

  • 偏向锁:在只有一个线程执行同步块的时候提高性能,根据对象头中MarkWord存储的偏向锁线程ID,当同ID再次进入的时候取消CAS操做来加锁和解锁, 只需要比较线程ID即可,只有在出现竞争(检测到不同的线程ID)的时候进行释放biased lock,而且持有biased lock的线程不会主动的释放锁,当有竞争时首先会检查持有线程是否存活,若不存活则对象变为无锁状态,重偏向为竞争的线程,若持有线程仍存活则将偏向锁升级为轻量级锁,并且由原来持有偏向锁的线程持有轻量级锁,竞争线程进行自旋等待获取轻量级锁
  • 轻量级锁: 若当前只有一个线程等待,则通过自旋(线程不会进入阻塞而导致用户态和nnei内核态的转换,而是一直向CPU发请求获取锁)等待获取锁,若自旋时间过长或者有新的线程参与竞争则升级为重量级锁
  • 锁消除: 虚拟机即时编辑器在运行时,对一些“代码上要求同步,但是被检测到不可能存在共享数据竞争”的锁进行消除。
  • 锁粗化: 如果虚拟机检测到有一串零碎的操作都是对同一对象的加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。

参考链接

  1. https://mp.weixin.qq.com/s?__biz=MzI3NzM2OTQ5Mg==&mid=2247484280&idx=1&sn=8de305338c5ab348c3e2a784084e4306&chksm=eb660483dc118d95e9bcde15a01103f818ed2fd399989f36dc2d57740a305e91cf986d4f5a64&scene=21#wechat_redirect

 评论


博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 Material X 作为主题 , 总访问量为 次 。
载入天数...载入时分秒...