synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解

目录

0、基础知识:Java对象的存储格式

1. synchronized底层:Monitor(重量级锁):被锁的对象与Monitor的关系 

2. synchronized底层:轻量级锁优化,栈帧与被锁的对象的关系

3. 锁膨胀(轻量级锁升级为重量级锁)

4. 自旋优化---对“重量级锁”的优化

5. 偏向锁----对“轻量级锁”的优化

        5.1 偏向锁的核心思想 

        5.2 偏向状态

        5.3 偏向撤销   

        5.4  批量重偏向 与 批量撤销


0、基础知识:Java对象的存储格式

        在JVM中,对象拥有一个“对象头”,格式如下,其中包括了标记字(Mark Word)和类型指针(Class Pointer),Klass Word指向的是该对象所属的类,而Mark Word中记录了对象的一些状态信息,包括对象的哈希码、锁状态和GC相关信息等。

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第1张图片

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第2张图片

1、 synchronized底层:Monitor(重量级锁):被锁的对象与Monitor的关系 

         每个Java对象可以关联一个Monitor对象,其内部有如下图中的这些属性,Owner用来标记 这个Monitor的所有者是谁。
(1)当有线程(称为Thread-2)运行如下程序时,即在给对象obj上锁,此时,该对象头的Mark Word,就会被设置成一个指向Monitor对象的指针。

synchronized(obj){
    ....
}

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第3张图片

         此时,Thread-2会变为Monitor的Owner。

(2)当此时,有别的线程(Thread-1)运行到代码块时,会检查这个obj有没有关联Monitior(有无指向?),发现已经关联了(看MarkWord里的01、00、10)、并且Owner已经有主人了,此时将Thread-1放入EntryList阻塞队列,并且该线程进入Blocked状态。若还有别的线程,则同上。

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第4张图片

 (3)当Thread-2代码块运行结束后,断开与Monitor的连接,此时会通知MonitorEntryList,将这些线程唤醒,(此时是不公平竞争),可能Thread-1就成为了Monitor新的主人,其余线程仍然Blocked

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第5张图片

2、 synchronized底层:轻量级锁优化,栈帧与被锁的对象的关系

        轻量级锁:如果一个对象虽然是多线程访问,但多线程访问的时间是错开的(没有竞争),则可以用轻量级锁来优化,此时不需要Monitor

static final Object obj = new Object();
public static void method1(){
    synchronized(obj){
    //同步块A
    method2();
}
public static void method2(){
    synchronized(obj){
    //同步块B
    ...
}

        当线程调用方法,会创建一个栈帧,栈帧里包含一个锁记录的结构:
        主要内容有:(1)锁记录的地址(2)对象指针
步骤1:
        当执行到加锁的时候:做如下处理,并尝试将LockRecord的地址与 Object的MarkWord做交换!

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第6张图片

         如果标记位为01(Normal)状态,则可以交换(反之如果不是01,则有两种可能:
                1.如果有其他锁获得这个对象了,则 将会进行“锁膨胀”;
                2.
如果是自己又一次获得锁(锁重入),添加一条锁记录),
        交换后,对象头中的状态便由01变为00。

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第7张图片

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第8张图片

          步骤2:当执行完相应线程的代码,则释放锁记录的内存:synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第9张图片

 3、锁膨胀(轻量级锁升级为重量级锁)

        承接上文,如果发生如下情况时,则会进入锁膨胀过程
synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第10张图片

过程:    

  •  轻->重:申请Monitor锁,Object的MarkWord不再指向锁记录,而指向Monitor对象(如第1节所讲的那样),并且最后两位会变成10(重量级锁状态)
  •  Thread-1这个被阻塞的线程,自己进入EntryList,进入Blocked状态(如第1节所讲的那样)
  •  该Monitor的Owner是Thread-0
  • synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第11张图片

解锁: 步骤同前文的第1节

4.    自旋优化---对“重量级锁”的优化

        线程的阻塞与运行将带来上下文的切换,耗费资源。自旋优化能够缓解阻塞:
        概括:在获取monitor时如果失败了,不立刻进入EntryList阻塞,而是自旋重试几次(如果试了几次都还是失败,再去阻塞)

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第12张图片

5. 偏向锁----对“轻量级锁”的优化

5.1 偏向锁的核心思想 

        在上文中可知,轻量级锁在没有竞争时(还是这个线程来获取锁),将会发生锁重入,要执行CAS操作,并在栈桢创建新的锁记录。这样耗费了资源。
        优化方式:只有第一次使用CAS将线程ID设置到被锁对象的MarkWord中,以后再来的线程只用验证这个线程ID是自己则没有竞争,无需CAS。
        以后只要不发生竞争,这个对象归线程所有。

5.2 偏向状态

        biased_lock的0、1表示着:不是or是 偏向锁

synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第13张图片synchronized 底层实现原理、重量级锁、轻量锁、锁膨胀、锁自旋、偏向锁详解_第14张图片5.3 偏向撤销   

  1. 当有其他线程尝试获取同一个对象的锁时,偏向锁会被撤销。

  2. 当一个线程访问同步块时,如果检测到该对象处于偏向状态,并且线程 ID 不匹配,也会触发偏向锁撤销

  3. 调用wait/notify:只有重量级锁才有这样的机制

5.4  批量重偏向 与 批量撤销

        如果被锁的对象虽然被多个线程访问,但没有发生竞争(例如,先让偏向锁的线程全都执行完了,再让别的一些线程来获取这个锁),这时偏向锁的偏向可能发生改变:
        当撤销偏向锁阈值超过20次,jvm会认为自己偏向错了,于是回重新偏向。

        当撤销偏向锁的次数超过40次,jvm会认为不该偏向,则整个类所有对象会变为不可偏向(即MarkWord最后三位变为001

  

你可能感兴趣的:(java,jvm,算法,分布式,架构,后端)