重量级锁的8连问

1. 重量级锁的ObjectMonitor和JUC中的AQS有什么异同?

都是通过共享变量和等待队列实现的。
共享变量
ObjectMonitor通过共享变量owner(CAS将owner从null设置成为当前线程来抢锁),同时需要额外字段记录锁的重入次数。
AQS是通过共享变量state(CAS将state从0设置成1抢锁),同时需要额外字段exclusiveOwnerThread来记录当前持有锁的线程;
队列
ObjectMonitor等待队列使用了两个队列,cxq和entryList。
AQS需要一个队列CLH(先进先出)。
唤醒
ObjectMonitor可以实现精准唤醒;AQS不支持。
精准唤醒demo

2. 为什么ObjectMonitor需要cxq和entryList两个等待队列

ObjectMonitor中加解锁、wait/notify都涉及对等待队列的进出队操作。如果使用一个队列冲突的概率会加大,耗费系统资源。分成2个队列后,进出队EntryList队列只有加锁的情况才会操作,不需要CAS和自旋,减少了资源消耗。

3. cxq队列中等待线程,什么时候会进到EntryList

抢锁线程在获取锁失败后,默认会进cxq队列。当持有锁的线程执行完释放锁时,会将cxq中的等待节点放入EntryList中。就是说cxq->EntryList这一步是锁释放之前的由持有锁的线程做的。

4. 等待队列中多个线程,唤醒的顺序是什么

当持有锁的线程释放锁时,会先检查EntryList是否为空,如果不为空则唤醒EntryList中第一个节点。否则唤醒cxq中第一个节点。

5. 偏向锁和轻量级锁下线程是否可以wait和notify

6. cxq和waitset数据结构有什么区别

cxq是一个单向链表,采用先进后出的策略,就是说后入队的线程将先获取到互斥锁。
waiset是一个回环链表,即尾节点的下一个节点是头节点,采用先进先出的策略。

7. 被唤醒的wait线程和其它等待线程,谁会先抢到锁

使用notify和notifyAll唤醒wait线程,jvm的处理是有区别的。
1)如果是notify,唤醒的是waitset的队首节点,如果这时候EntryList不为空,则放入EntryList,否则放入cxq。无论是放入那个队列。因为是cxq后进先出,所以被唤醒的线程比等待队列中的线程先出队,会先抢到锁。
2)如果是notifyAll,会将waitset中的所有节点逐个放入cxq中。按照问题4中的描述,如果EntryList不为空,则EntryList中首节点会先抢到锁,否则waitset中原最后一个节点先抢到锁,如下图所示:
在这里插入图片描述

8. synchronized有类似AQS的公平锁/非公平锁逻辑吗

默认情况下,线程进入重量级锁的抢锁阶段,第一步就会尝试通过自旋来抢锁,所以默认相当于AQS中的非公平锁。即使自旋时未抢到锁,按照上面讲的cxq出入队逻辑,也是后进先出,正常情况下后进入等待队列的线程会先抢到锁,这一点也是和AQS中相反的。

猜你喜欢

转载自blog.csdn.net/yzx3105/article/details/129703021