Synchronized的锁升级过程

 

synchronized锁的是对象,所以我们是通过对象里面的对象头来判断是否有锁。

对象头

 

 无锁和偏向锁的锁标志位都为01,轻量级锁为00,重量级锁为10。无锁和偏向锁通过倒数第三位来判断是否是偏向锁。

 

无锁升级偏向锁过程:

线程A执行到同步代码块时,检查对象头锁标志位是否为01,再看偏向锁标志位是否为0(即检查对象是否为无锁状态),通过CAS操作尝试修改MarkWord字段,这里CAS操作只尝试一次,

失败的话说明发生锁竞争,立即升级为轻量级锁(说明此时已经升级为偏向锁了)。(也就是锁撤销的第2种)

成功修改后升级为偏向锁(线程id、是否为偏向锁)。
————————————————

偏向锁到轻量级锁过程:

线程B执行到同步代码块时,检查到对象头锁标志位为01且偏向锁标志位为1,说明该锁已被线程占有。检查Thread Id是否为当前线程(B线程),是的话就执行同步代码块,否则就进入锁撤销逻辑,jvm会在安全点暂停所有线程,判断持有锁线程的当前状态,分两种情况:

  1.线程已死亡或没在执行同步代码块: JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。进行锁撤销操作,对象由偏向锁变为无锁且不可偏向状态,线程B通过CAS操作尝试修改Thread Id失败升级为轻量级锁,成功则执行同步代码块。


  2.线程正在执行同步代码块: 说明发生了锁竞争,进行锁撤销,将对象头MarkWord置为无锁状态并升级为轻量级锁。

    最终都会升级到轻量级锁。

 

  但是在升级为轻量级锁的过程中,JVM会在当前线程的栈帧中开辟一块区域,用于存储锁记录(Lock Recored)的空间,并将对象头中的MarkWord复制到锁记录中,然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。

  如果成功,当前线程获得锁,执行同步代码块。(当线程再次进入同步代码块时,只需检查对象头中的MarkWord中的指针是否指向当前线程的锁记录,不是的话执行自旋CAS操作,是的话直接执行同步代码块)

  如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。多次自旋仍未获取锁则锁升级为重量级锁。


------------------------------------------

轻量级锁到重量级锁过程:

  

  轻量级锁的分类?

  轻量级锁有两种:自旋锁,自适应自旋锁。

     对于自旋锁就是一个线程正持有该锁,另外一个线程进来了就等着,相当于执行一个循环,只到成功获取到了锁。但是循环是要消耗资源的,不可能让线程在那一直循环,就设置了一个固定的循环次数10,超过了10回就认为你获取不到该锁了,执行下一步操作,比如升级成重量级锁。

     对于自适应自旋锁,没有固定的循环次数,而是对于每个锁有一个学习能力,就是说对于某个锁,一个线程循环了5次拿到了,那么下一个线程也循环5次去获取这个锁,获取不到,就直接转成重量级锁。

 

  线程C执行到同步代码块时,检查到对象头锁标志位00,说明该锁为轻量级锁。通过CAS操作尝试修改MarkWord字段,因为自旋的原因这里尝试多次CAS操作,多次尝试都失败的话将锁升级为重量级锁,线程睡眠等待被唤醒。自旋成功则执行同步代码块,执行完毕后释放锁恢复MarkWord数据,然后检查锁是否升级为重量级锁(线程执行同步代码块的时候可能有其他线程过来抢占锁且不成功,这时锁会升级为重量级锁)。如果升级为重量级锁的话还要执行一步操作 唤醒其他线程。

 

重量级锁是通过一个监视器锁对象monitor实现的,monitor随着对象的创建而创建。线程获取锁可以理解为获取monitor,成功之后会对monitor对象加一,释放掉了就减一。这种加一减一实际上是通过操作系统的mutex(互斥)、lock实现的。操作系统实现mutex、lock需要从用户态转换成核心态,成本比较大,耗费时间长。这就是synchronized效率低的原因。1.6之前,synchronized主要是依靠操作系统的mutex、lock指令,比较耗费资源,之后引入了偏向锁和轻量级锁的概念,提高了效率,这也是1.8中,concurrenthashmap使用synchronized锁的原因。

 

原文链接:https://blog.csdn.net/qq_26024785/article/details/123224843

posted @ 2022-07-02 16:30  WXY_WXY  阅读(271)  评论(0编辑  收藏  举报