【JUC】Synchronized与锁升级
文章目录
1. 概述
用锁能够实现数据的安全性,但是会带来性能下降。无锁能够基于线程并行提升程序性能,但是会带来安全性下降。如何达到两者的平衡呢?
synchronized
用的锁是存在java对象头里的
Mark Word
中。锁升级功能主要依赖
Mark Word
中锁标志位和释放偏向锁位
偏向锁:
Mark Word
存储的是偏向的线程ID。
轻量锁:
Mark Word
存储的是指向线程栈中 Lock Record 的指针。
重量锁:
Mark Word
存储的是指向堆中的 monitor 对象的指针。
1.1 无锁
publicclassMyObject{publicstaticvoidmain(String[] args){Object o =newObject();System.out.println("10进制hash码:"+o.hashCode());System.out.println("16进制hash码:"+Integer.toHexString(o.hashCode()));System.out.println("2进制hash码:"+Integer.toBinaryString(o.hashCode()));System.out.println(ClassLayout.parseInstance(o).toPrintable());}}
运行结果如下所示:
注:
Mark Word
怎么看呢?
总体上是由右下往左上看,每8位从左往右看。所以前25位是没用的,都是0。根据规则,第25位是第二行第一个数字也是0。哈希码是第26位(第2行第2位)至第57位(第1行第9位)。偏向锁位第62位(第一行第6位),锁标志位第63位至第64位(第一行第7位至第8位)。
1.2 偏向锁
偏向锁:当一个线程第一次获得锁时,JVM会将锁对象头中的标志位设置为“偏向模式”,同时将其线程ID记录在对象头中。此后,如果这个线程再次请求锁,JVM会直接将锁分配给该线程,跳过竞争锁的过程,从而避免了无谓的锁竞争和线程上下文切换,提高了程序的执行效率。
使用条件:
- 大部分情况下,锁只被一个线程获取。
- 对象的生命周期内,锁定操作仅在少数时间发生。
偏向锁的出现是为了解决只有在一个线程执行同步时提高性能。
在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就算锁的偏向线程。
那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接回去检查锁的
Mark Word
里面是不是放的自己的线程ID)。
如果相等,表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外的开销,性能极高。
如果不等,表示发生了竞争,锁已经不是总是偏向于同一个线程了,这个时候会尝试使用CAS来替换
Mark Word
里面的线程ID为新线程的ID。
竞争成功,表示之前的线程不存在了,
Mark Word
里面的线程ID为新线程的ID,锁不会升级,仍然为偏向锁。
竞争失败,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。
注意:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
技术实现:
一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的
Mark Word
中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需要去对象头的
Mark Word
中去判断一下是否有偏向锁指向本身的ID,无需再进入
Monitor
去竞争对象了。
版权归原作者 嗯mua. 所有, 如有侵权,请联系我们删除。