天天看点

ReentrantLock 加锁解锁过程分析

一、ReetrantLock

     1、 公平锁,ReentrantLock reentrantLock = new ReentrantLock(true);  reentrantLock.lock();设置锁为公平锁。 

ReentrantLock 加锁解锁过程分析
ReentrantLock 加锁解锁过程分析

            1-1、 内部获取定义的变量state,判断是否为0,如果不为0,则代表锁已经被占用

           1-2、判断是否需要排队:

                   ① 判断队首,队尾是否相同,队列未初始化时,队列为空,队首队尾相同,返回fasle。

                   ② 队列已经初始化,但是队列中只有一个空的node节点,没有排队的线程,队首队尾相同,返回fasle。

                   ③ 队列已经初始化,队首空node下有一个新的node,并且是这个node中的线程获取锁,返回false。

这里如果返回false,!hasQueuedPredecessors返回true,则代表可以获取锁

ReentrantLock 加锁解锁过程分析

       1-3 调用unsafe方法,修改state的值为1。

ReentrantLock 加锁解锁过程分析

     1-4、设置持有锁的线程为当前线程,加锁成功,返回true。!tryAcquire(arg) 结果为false,执行 selfInterrupt();回复线程实际的执行状态。

   2、 如果1 中的条件不成立,则标识获取锁失败,需要进行排队尝试。

      2-1 addWaiter(Node.EXCLUSIVE), arg),尝试初始化排队队列。

ReentrantLock 加锁解锁过程分析

         ① 如果队列已经初始化,队尾增加一个node,维护一个双向链表。

         ② 如果队列未初始化,则需要初始化一个队列,这里是一个死循环,初始化完成跳出,判断队尾为null,则在队首初始化一个线程为空的node(表示队列中可能正在运行的线程,或者是持有锁的线程),把,新的node追加到初始化的node之后。

ReentrantLock 加锁解锁过程分析

 维护一个如下图一样的队列,队首的node的thread一直为null。

ReentrantLock 加锁解锁过程分析

3 获取队列

   3-1 查看一下当前node的前一个是否是队首,如果不是则 证明前面已经有人在排队了,直接park

   3-2 如果前一个是队首,则在此尝试获取锁,如果能 获取,则返回表示加锁成功。

   3-3 如果都没有成功,则修改前一个waitStatus,为-1,如果大于0,表示前一个node已经被cancled,则移除前一个node,直到找到一个可用的node。并且LockSupport.park(this);线程让出cpu,等待唤醒。

ReentrantLock 加锁解锁过程分析
ReentrantLock 加锁解锁过程分析

二、解锁过程:reentrantLock.unlock();

ReentrantLock 加锁解锁过程分析

 1、尝试解锁,比较简单,对state -1,如果值为0,则代表可以释放,清空当前运行线程,并返回true

ReentrantLock 加锁解锁过程分析

  2、unparkSuccessor(h)释放下一个线程     

if (ws < 0) compareAndSetWaitStatus(node, ws, 0); 这个是将头节点设置状态设置为0,

① 下次节点进才会把头改为-1,才证明有新的线程加入,否则一直为0,

② 下面代码判断的时候如果只有一个头节点,node.next也为null,头尾一个节点,t!= null,但是头的节点的thread为null,如果①步骤不改,调用unpark会报错。

ReentrantLock 加锁解锁过程分析