(11)重量级锁(Monitor)的加锁和解锁流程?解锁是有顺序的吗?

文章详细阐述了Java中重量级锁(Monitor)的加锁和解锁过程,包括线程t1、t2、t3获取锁的顺序,自旋、预自旋和阻塞的概念,以及锁释放后的唤醒策略。当锁释放时,线程在cxq中的顺序决定了后续获取锁的顺序,展示了非抢占式和抢占式锁的转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重量级锁(Monitor)加锁流程和解锁流程

重量级锁 MonitorObject 对象有 4 个属性,分别是:

  • _owner:当前锁的持有线程

  • _cxq:竞争栈

  • _entryList:一个队列

  • _waitSet:

加锁流程:

        当线程 t1、t2、t3 一起获取一个重量级锁时,获取的时间顺序分别是 t1、t2 和 t3。

1、因为是线程 t1 首先到达,所以 t1 会获取成功, MonitorObject 的 _owner 会从 nullptr 变成 t1,线程 t1 的 markword 对象存储 MonitorObject 的地址引用并将最后两位标记为 10,表示重量级锁。此时线程 t2 和 t3 肯定获取锁失败。

2、线程 t2,t3 开获取失败后悔开始进行自旋操作【jdk1.6 以后固定自旋就弃用了】,首先预自旋 11 次,获取锁失败之后会自适应自旋,首先自旋 5000 次。在自旋期间若线程 t1 释放锁了,此时线程 t2 和 t3 会一起去抢占锁,若没有释放就会进入 _cxq 竞争栈中。这段时间还是抢占式的。

3、若第二步获取锁失败了,就会进入一个叫做 enterI 的方法,尝试获取 _owner ,失败之后会陷入自旋,较上次自旋次数少 200 次,若自旋期间获取成功就成功拿到锁了,这段时间还是抢占式的。

4、若第 3 步中还是获取失败了那么线程就会在 _cxq 中陷入阻塞状态了(park),直到 _owner 被释放才会被唤醒。从这里开始就是非抢占式的了。靠后竞争锁的线程会优先获取到锁。

解锁流程:

        当线程 t1 释放锁之后,就会将 _owner 设置成 nullptr。此时会根据 _cxq 和 _entryList 的状态做出不同的操作。

1、当_cxq 和 _entryList 都为空时直接返回,释放成功。

2、当 _cxq 不为空时,就会将 _cxq 中所有的节点移动到 _entryList 中,_cxq 按照后进先出的原则,之后进入 _cxq 的会先进入 _entryList。

3、当 _entryList 不为空时,使用 unpark 方法从队列头结点开始唤醒,然后返回。

所以说,根据上面的加锁流程,当 t1 释放锁之后,进入 _cxq 的顺序是先 t2 后 t3,所以离开 _cxq 进入 _entryList 的顺序是先 t3 后 t2。故在 t2 和 t3 中,t3 会先获得锁。

解锁是有序的验证

查看下列代码

   
 private static Object obj = new Object();
​
    public static void main(String[] args) throws InterruptedException {
​
        new Thread(() -> {
            System.out.println("t1 获取锁");
            synchronized (obj) {
                try {
                    System.in.read();
                    System.out.println("t1 释放");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }).start();
​
        Thread.sleep(100);
        new Thread(() ->{
            synchronized (obj){
                System.out.println("t2 获取");
            }
        }).start();
​
        Thread.sleep(100);
        new Thread(() ->{
            synchronized (obj){
                System.out.println("t3 获取");
            }
        }).start();
​
        Thread.sleep(100);
        new Thread(() ->{
            synchronized (obj){
                System.out.println("t4 获取");
            }
        }).start();
​
    }

运行结果如下(运行多次结果都是一样的):从结果可以看出,当线程 t1 释放锁后,越靠后竞争锁的线程或优先抢占到锁。这就是上面加锁流程中第 4 步的体现。

t1 获取锁
​
t1 释放
t4 获取
t3 获取
t2 获取
​
Process finished with exit code 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值