ReentrantLock加锁解锁的逻辑。
公平和非公平,可重入锁的实现。
并发场景下入队和出队的操作。
- public class ReentrantLockDemo1 {
- private static int sum = 0;
-
- private static Lock lock = new ReentrantLock();
-
- public static void main(String[] args) throws InterruptedException {
- for(int i=0;i<3;i++){
- Thread t = new Thread(()->{
- lock.lock();
- try {
- for(int j=0;j<10000;j++){
- sum++;
- }
- }finally {
- lock.unlock();
- }
- });
- t.start();
- }
- Thread.sleep(2000);
- System.out.println(sum);
- }
- }
一个简单的代码,我们打上断点,选择线程的模式,来看看线程内部是如何执行的。


两行代码。
compareAndSetState(0, 1)通过cas运算将锁状态由0改为1
setExclusiveOwnerThread(Thread.currentThread()) 将锁绑定到该线程,上锁完成。

通过上面两行逻辑完成上锁。此时thread0以执行完成,sum值为10000,但是暂未释放锁。我们选定thread1进入上锁逻辑,来看看thread1是如何执行的。

此时锁状态为1,cas失败,执行acquire逻辑。

我们跟进acquire方法,首先执行tryAcquire,我们跟进去看看
可见c是锁的状态,在thread0上锁时,已通过cas将其改为1,所以第一个if判断是不进入的。锁以绑定thread0,所以第二个if也是不进入的,直接返回false。第二个if判断就是可重入部分的逻辑。

判断条件为&&关联,!tryAcquire则为true,继续后一半判断逻辑,我们进入addWaiter方法

创建绑定当前线程的node节点。将pred节点设置为tail,此时tail为null,链表还未组成,进入enq入队方法。

发现进入一个自旋。首先将tail赋值给一个空节点t。如果t为空,则compareAndSetHead(new Node())设置头节点为空白节点。最后头,尾节点均未同一个空白节点构成链表。

然后进入下一循环, 此时t节点赋值tail则不为空(虽然这个节点的属性为空白),进入else结构。我们传入的需要入队的节点node的prev赋值t,通过cas将node节点设置为tail。最后将node节点赋值给t.next。此时完成了我们node节点入队的逻辑。我们得到了头结点为t,尾节点为我们入队的node节点的双向链表。

返回绑定thread1的node节点,我们进入acquireQueued的方法。首先获得条件等待队列的头结点p。在第一个if中会尝试再次加锁,失败返回false,跳入下个if判断中。

我们进入shouldParkAfterFailedAcquire方法。头结点是一个未传入任何属性的空白节点,该节点的waitStatus为0,通过cas我们将头节点的waitStatus改为可唤醒后面节点的状态-1并返回false。

因为返回false,则不执行后半部分判断,通过自旋开始下一次循环。此时的头节点p的waitStatus为-1,在次执行shouldParkAfterFailedAcquire()方法后返回true,我们进入parkAndCheckInterrupt()方法。

最后阻塞线程。

我们在来走thread2, 同样的进入acquire方法。

tryAcquire方法不成功返回false,进入addWaiter()的入队方法。此时pred节点等于尾节点就是绑定thread1的节点,pred不为空进入if逻辑。将当前节点的prev指向thread1的节点,通过cas将当前节点设置为尾节点,最后将thread1节点的next指向当前节点,完成当前节点的入队,返回当前节点。

最后就是讲thread1节点的waitStatus改为-1,并在第二轮循环中阻塞thread2线程。
我们在返回thread0来看解锁逻辑。可以看到就是讲锁状态置为0,同时将绑定的线程置为null,返回true。

进入if里面的逻辑,头结点不为空且状态不为0,执行unparkSuccessor(h)

进入第一个if判断,将头结点的waitStatus置为0。然后将thread1的node节点赋值给s节点,s节点不为空,唤醒s节点
此时thread1从park中唤醒

然后重新进行入队操作方法的循环中,获取当前节点的前一个节点p,p为头节点,然后尝试加锁。加锁成功后进入第一个if逻辑中。

setHead就是将当前节点(thread1)设置为头节点,并将当前节点所有属性置为null,包括绑定的thread。

然后将链表中第一个节点的next属性置为空,返回0。此时头结点出队,链表缩减为两个节点。

总结 :