开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第18天,点击查看活动详情
1.偏向锁升级过程
我们图中流程,我用文字叙述一下:
线程1访问同步代码块,检查对象头中是否存储了线程1,如果有,则直接获取锁;如果没有,则CAS替换MarkWord,里面前23位存储线程1的线程id,epoch记录偏向时间戳。此时可以执行同步代码块中的内容。
此时线程2也访问同步代码块,检查对象头中是否存储了线程2,发现没有,进行CAS替换MarkWord中的内容,不成功,则发起撤销偏向锁的请求。
此时线程1因为持有锁,会发现有偏向锁撤销请求,就把线程1执行到安全点。然后线程1进行解锁,把markword中的线程id信息设置为空,并恢复线程去竞争。
此时线程2也在争抢线程,线程1和线程2进行到了新一轮的争抢。
2.总结
结合前几篇的例子,我们可以总结出这么一段话:
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
以上这段话摘自方腾飞-并发编程艺术书。可以作为面试使用。
那么我们回归开始,为什么要有偏向锁?
因为我们在运行过程中会发现,大部分锁竞争只是被一个线程使用,很少出现锁竞争的情况,因为大部分同步代码块内容很简单,执行速度很快。所以偏向锁在获取锁的时候只用判断线程id即可,避免了内存消耗。