一、ReentrantLock的lock()梳理
首先贴下lock()源码调用时序图:
源码:
- 首先通过cas抢占锁
- 如果抢占失败,则执行acquire()方法
- tryAcquire方法:再抢占一次锁。会走到nofairTryAcquire方法,nofairTryAcquire方法的主要作用是再次抢占锁。分为两种情况,state为0和state不为0。state为0表示没有重入,state不为0且当前抢占锁的线程为当前线程,表示重入了锁,state需要+1。
- addWaiter方法:如果步骤1抢占锁失败,则需要将当前线程包装成Node,放到aqs同步队列的尾部。
- acquireQueued:在同步队列中抢占锁,只有head节点才能抢占锁,也就是说,在同步队列中,节点抢占锁的顺序是由链表的前往后。acquireQueued方法是包含一个死循环的,出口是只能是当前节点是头节点并抢到了锁,否则就挂起线程。
上述方法的具体代码我就不贴了,之前的文章分析过。 直接贴上之前文章的图解吧:
二、ReentrantLock的unlock()梳理
unlock方法主要做了两个动作:
- 释放锁,即state-1,并且exclusiveOwnerThread属性设置为null。
- 唤醒离head节点最近的waitStatus<=0的节点线程
三、加锁和释放锁流程图
四、公平锁和非公平锁的区别
- 非公平锁会先进行一次cas抢占锁,没抢占到才加入aqs队列。
- 公平锁,则会判断aqs队列当前节点是否有前驱节点,如果返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
五、在队列同步器中,同步队列为什么是双向链表,而等待队列是单链表?
在《Java并发编程的艺术》一书中有这样一段话:
在队列同步器中,头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态后,将会唤醒其他后续节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点,如果是则尝试获取同步状态。
所以为了能让后继节点获取到其前驱节点,同步队列便设置为双向链表,而等待队列没有这样的需求,就为单链表。