目录
前言
近日,看到有些文章说:“产生竞争时,轻量级锁升级为重量级锁,此时持锁线程执行 CAS 操作(锁重入) 会失败,所以将会提前释放锁”
首先,持锁线程可能连同步块都未执行完成就平白无故将锁释放掉,怎么想都不合理
接下来的实验将证明上述 “提前释放锁” 结论的错误性
实验
如何验证
锁升为重量级锁后,如果会释放锁,那么锁重入肯定会失败;所以只需验证升级之后,锁重入不会阻塞即可
流程说明
main
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
// Thread0 在方法中首次获取锁
first();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(5000);
new Thread(() -> {
log.debug("---------------------------Thread1 首次尝试加锁---------------------------");
synchronized (lock){
log.debug(ClassLayout.parseInstance(lock).toPrintable());
}
}).start();
}
- 创建 Thread0,执行包含获取锁操作的方法
- 主线程睡眠 5s,确保 Thread0 先执行,先获取锁,并且此时为轻量级锁
- 创建 Thread1,尝试获取锁,产生锁竞争,导致锁升级
first
static void first() throws InterruptedException {
log.debug("---------------------------Thread0 首次尝试获取锁---------------------------");
synchronized(lock){
log.debug("Thread0 首次获取锁成功!");
log.debug(ClassLayout.parseInstance(lock).toPrintable());
Thread.sleep(10000);
// 第二次获取锁(锁重入)
reentrant();
}
}
- Thread0 首次获取锁,打印锁对象的对象头
- 先睡眠 10s(Thead1 在此期间被创建并尝试获取锁,导致发生锁升级)
- 然后调用 reentrant() 方法,进入该方法会再次执行一次获取锁的操作
reentrant
static void reentrant(){
log.debug("---------------------------Thread0 尝试锁重入---------------------------");
synchronized(lock){
log.debug("Thread0 锁重入成功!");
log.debug(ClassLayout.parseInstance(lock).toPrintable());
}
}
- Thread0 尝试再次获取锁(锁重入)
- 若未阻塞,则会执行打印,验证了结论的错误性
- 若阻塞,代表锁已释放并被其他线程获取,说明结论是正确的
结果
- 红色框:表示 Thread0 的时间线
- 蓝色框:表示 Thread1 的时间线
- 紫色框:表示锁的状态
时间线:
第 26s 主线程创建 Thread0 并启动,然后进入 5s 睡眠;Thread0 尝试第一次获取锁,成功了,锁状态 00 代表轻量级锁,然后进入 10s 睡眠
5s 之后的第 31s,主线程创建 Thread1 并启动;Thread1 尝试第一次获取锁,由于 Thread0 还在睡眠中,并且未释放锁,此时发生锁竞争,锁升级为重量级锁;Thread1 获取锁失败,进入阻塞
10s 之后的第 36s,Thread0 执行锁重入,成功了,锁状态 10 代表已升级为重量级锁;Thread0 释放锁,Thread1 从阻塞中被唤醒,并且获取锁成功
总结
实验结果表明,即使锁已经升级为重量级锁,持锁线程执行锁重入操作并未发生阻塞,所以也就不存在释放锁一说
锁已经升级,但是持锁线程可能确实并不知情,本人猜测:在锁重入或者解锁时发现已经升级为重量级锁,并且接下来不是对锁对象的对象头执行 CAS 操作,而是对它指向的 monitor 对象中的 owner 属性执行 CAS 操作
(补充:轻量级锁,锁重入执行 CAS 操作都会失败,但是它只会生成锁记录,而不是直接释放锁)
并发编程 synchronized (五) 重量级锁、轻量级锁_皮皮杨233的博客-优快云博客_synchronized轻量级锁 重量级锁