对象从无锁到偏向锁转化的过程

2024-08-04 20:04:47 浏览数 (1)

64位JVM下的对象结构描述: 对象头的最后两位存储了锁的标志位 没加锁状态,锁标志位01,是否偏向是0,对象头里存储的是对象本身的哈希码。 偏向锁状态,锁标志位01,是否偏向是1,存储的是当前占用对象的线程ID。 轻量级锁状态,锁标志位00,存储指向线程栈中锁记录的指针。 重量级锁状态,锁标志位10,存储的就是重量级锁的指针了。

对象从无锁到偏向锁转化的过程 第一步,检测MarkWord是否为可偏向状态,是偏向锁是1,锁标识位是01。 第二步,如果是可偏向状态,测试线程ID是不是当前线程ID。如果是,就直接执行同步代码块。 第三步,如果测试线程ID不是当前线程ID,就通过CAS操作竞争锁,竞争成功,就把MarkWord的线程ID替换为当前线程ID。 第四步,如果CAS竞争锁失败,证明有别的线程持有锁,假设线程B来CAS失败了,这个时候启动偏向锁撤销(revokebias),让A线程在全局安全点阻塞,获得偏向锁的线程被挂起,有点类似于GC前线程在安全点阻塞。 第五步,接着遍历线程栈,查看有没有锁对象的锁记录LockRecord,如果有LockRecord,需要修复锁记录和Markword,让它变成无锁状态。恢复A线程,将是否为偏向锁状态改为0,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程,继续往下执行同步代码块。

安全点是jvm为了保证在垃圾回收的过程中引用关系不会发生变化,设置的安全状态,在这个状态上会暂停所有线程工作。一般有循环的末尾,方法临返回前,调用方法的call指令后,可能抛异常的位置,这些位置都可以算是安全点。

轻量级锁升级 轻量级锁升级过程是,在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的MarkWord的拷贝,拷贝无锁状态对象头中的MarkWord复制到锁记录中。

这么做是因为在申请对象锁时,需要以该值作为CAS的比较条件。 同时在升级到重量级锁的时候,能通过这个比较,判定是否在持有锁的过程中,这个锁被其他线程申请过,如果被其他线程申请了,在释放锁的时候要唤醒被挂起的线程。 无锁的markword中可能存有hashCode,锁撤销之后必须恢复,这个markword要用于锁撤销后的还原。如果轻量级锁解锁为无锁状态,直接将拷贝的markword CAS修改到锁对象的markword里面就可以了。 拷贝成功后,虚拟机将使用CAS操作把对象中对象头MarkWord替换为指向锁记录的指针,然后把锁记录空间里的owner指针指向加锁的对象,如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象MarkWord的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。

如果这个更新操作失败了,虚拟机首先会检查对象MarkWord中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。如果不是说明多个线程竞争锁,进入自旋,若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,MarkWord中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

当锁升级为轻量级锁之后,如果依然有新线程过来竞争锁,首先新线程会自旋尝试获取锁,尝试到一定次数(默认10次)依然没有拿到,锁就会升级成重量级锁。一般来说,同步代码块内的代码应该很快就执行结束,这时候线程B自旋一段时间是很容易拿到锁的,但是如果不巧,没拿到,自旋其实就是死循环,很耗CPU的,因此就直接转成重量级锁咯,这样就不用了线程一直自旋了。

0 人点赞