开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第28天,点击查看活动详情
上篇文章说到加锁过程,demo如下:
public class Demo{
private List<String> list = new ArrayList<>();
public static void main(String[] args) {
Demo Demo = new Demo();
for (int i = 0; i < 50; i++) {
Demo.add("Demo--->" + i);
}
}
public synchronized void add(String s) {
list.add(s);
}
}
解锁过程
当有其他线程尝试获得锁时,是根据遍历偏向线程的lock record来确定该线程是否还在执行同步块中的代码。因此偏向锁的解锁很简单,仅仅将栈中的最近一条lock record的obj字段设置为null。偏向锁的解锁步骤中并不会修改对象头中的thread id。
偏向锁升级时机
一般来说(批量重偏向除外),偏向锁升级的时机为:当锁已经发生偏向后,只要有另一个线程尝试获得偏向锁,则该偏向锁就会升级成轻量级锁。
偏向锁撤销过程
这里说的撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态;释放是指退出同步块时的过程。
撤销逻辑有很多,我们只分析最常见的情况:假设锁已经偏向线程A,这时B线程尝试获得锁。
- 查看偏向的线程是否存活,如果已经不存活了,则直接撤销偏向锁。JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。
- 偏向的线程是否还在同步块中,如果不在了,则撤销偏向锁。我们回顾一下偏向锁的加锁流程:每次进入同步块(即执行
monitorenter)的时候都会以从高往低的顺序在栈中找到第一个可用的Lock Record,将其obj字段指向锁对象。每次解锁(即执行monitorexit)的时候都会将最低的一个相关Lock Record移除掉。所以可以通过遍历线程栈中的Lock Record来判断线程是否还在同步块中。 - 将偏向线程所有相关
Lock Record的Displaced Mark Word设置为null,然后将最高位的Lock Record的Displaced Mark Word设置为无锁状态,最高位的Lock Record也就是第一次获得锁时的Lock Record(这里的第一次是指重入获取锁时的第一次),然后将对象头指向最高位的Lock Record,这里不需要用CAS指令,因为是在safepoint。 执行完后,就升级成了轻量级锁。原偏向线程的所有Lock Record都已经变成轻量级锁的状态。【轻量级锁加锁过程会在下文讲到,不要慌】
触发时机:
释放:对应的就是synchronized方法的退出或synchronized块的结束。
撤销:笼统的说就是多个线程竞争导致不能再使用偏向模式的时候。