前言
前几天面试了好几位小伙伴,在问到关于锁这一块的知识点时,大部分人都会有意无意的提到一个点:ReentrantLock是轻量级锁,相比于synchronized实现更加简单(或者说比synchronized轻量级)。
对于这样的说法,我通常会继续追问以下几个问题:
- 你是从什么角度来判断ReentrantLock是轻量级锁的?
- 那如果某个线程CAS失败了,这个线程会怎么处理?
- 既然是通过LockSupport进行阻塞的,那你知道LockSupport原理吗?
- 既然LockSupport也是通过mutex,那为什么还说ReentrantLock是轻量级锁?
这几个问题能接上来的不多,如果你能全部答上来,那么说明你对这一块比较清晰,在面试中一般可以乱杀。
AQS流程处理
我们先回答前面两个问题,不过回答这两个问题之前,我们先大概梳理一下AQS对加锁和解锁的关键点。
加锁关键点
不说代码流程中的细节,我们就第二个问题来分析,加锁时如果CAS失败了会怎么样呢?
观察这段代码,如果尝试CAS失败之后,会调用parkAndCheckInterrupt方法,点进去可以看到,CAS失败后,会封装成一个Node类型的对象加入CLH队列中,然后调用LockSupport.park(this)进行阻塞:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
到这里我们先简单得出一个结论:CAS失败后会通过LockSupport.park(this)对当前线程进行阻塞
解锁关键点
既然已经知道加锁流程中是通过LockSupport进行阻塞的,那么解锁流程不用想,肯定也在某个地方进行了unpark,先看看解锁的模板方法:
![](https://i-blog.csdnimg.cn/blog_migrate/811449bdb84dce7988b1ac2a6cac21a9.png)
在unparkSuccessor方法中,的确是有调用unpark方法进行解锁:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
LockSupport原理
从上面可以知道,AQS虽然有用CAS进行尝试加锁,但是加锁失败后还是通过LockSupport来实现阻塞和解锁的。
那么LockSupport又是通过什么实现阻塞的呢?可惜我们点进去源码会发现它是一个native方法,通过观察源码我们可以发现,实际上阻塞park和唤醒unpark是用到了mutex和condition的方法调用
LockSupport中的park与unpark原理
JUC—LOCKSUPPORT以及PARK、UNPARK方法底层源码深度解析
感兴趣的同学可以去阅读一下这两篇文章,大佬级别。到这里我们又得出来一个结论:
LockSupport也是基于mutex实现的
要知道,synchronized的重量级锁底层依赖的是mutex lock,会有用户态和内核态的切换,才会有AQS的一堆优化,然而我们会发现,AQS也是通过mutex来实现的!
给面试官的答案
到这里我们好像可以给面试官满意的答案:
实际上ReentrantLock在CAS加锁失败之后会封装成一个Node类型的对象加入CLH队列中,然后调用LockSupport.park(this)进行阻塞
而LockSupport是一个native方法实现的工具类,在hotspot源码中也是通过mutex来实现的,一些情况下它的开销可能不会比Synchronized好
可惜我还是会接着问你最后一个问题:
那么ReentrantLock和Synchronized我们该如何选择呢?
这里我也给一份比较好的答案(我抄来的~):
Lock是通过自旋CAS和Unsafe.park/unpark挂起唤醒线程来实现的,而synchronized在jdk1.6后重量级锁也是通过CAS自旋以及park/unpark来实现的,都有进行用户态和内核态的切换。
但是synchronized做了优化后在前面的偏向锁拿到锁的线程不会进行CAS自旋,而轻量级锁也只是进行CAS自旋不会阻塞挂起,只有膨胀到重量级锁后才会自旋CAS+park/unpark来挂起唤醒线程!
所以理论上synchronized的效率应该比Lock快一点点,但是Lock提供的API比较简单方便!