ReentrantLock加锁解锁过程

公平锁

调用lock方法加锁

 

进入acquire方法获取加锁的许可

 

 进入tryacquire

 

 首先获取当前线程和status状态,status默认为0

假如现在t1线程进入,然后t2线程进入(t2进入时t1还没有释放锁)

if c==0成立,然后判断是否需要排队,调用hasqueuedpredecessors方法

 

 此时的头和尾都是null,此方法返回false,所以上面if(!hasqueuedpredecessors())成立,然后进行cas操作,将status改为1

然后设置持有锁的是当前线程。最后返回true。即t1拿到锁继续执行自己的业务逻辑。。。。

1、如果t2执行lock方法的时候t1已经释放锁,也就不会存在竞争,一次执行。

2、如果t1还没有释放锁,t2也会走上面的代码。

走到tryacquire方法尝试去获取锁的时候肯定失败,因为t1还在占用。方法返回false。

 

 然后会执行addwriter方法进入队列排队。

 

 首先创建一个节点node,节点里包含属性thread,pre,next,是一个双向链表

由于tail=null,所以会走enq方法。

 

 此处无限循环,t==null也成立,首先会先初始化一个新的Node,node里的信息目前为空。然后设置为头部。(其实队列里的头部永远都是一个空节点,空节点的意思是有node对象,只不过里面的thread属性为空)

继续循环,走else,里面的逻辑意思是将自己的节点设置为尾部,空节点设置为头部,然后将空节点的next指向t2节点,t2的pre指向头部的空节点。

最终的关系如下图

 

 

继续走acquirequeued方法

 

 

 1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 2         int ws = pred.waitStatus;//等待状态,默认为0,Node.SIGNAL=-1
 3         if (ws == Node.SIGNAL)
 4             /*
 5              * This node has already set status asking a release
 6              * to signal it, so it can safely park.
 7              */
 8             return true;
 9         if (ws > 0) {
10             /*
11              * Predecessor was cancelled. Skip over predecessors and
12              * indicate retry.
13              */
14             do {
15                 node.prev = pred = pred.prev;
16             } while (pred.waitStatus > 0);
17             pred.next = node;
18         } else {
19             /*
20              * waitStatus must be 0 or PROPAGATE.  Indicate that we
21              * need a signal, but don't park yet.  Caller will need to
22              * retry to make sure it cannot acquire before parking.
23              */
24             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
25         }
26         return false;
27     }

所以会走到compareAndSetWaitStatus方法将上一个节点的waitstatus改为-1,然后返回false,由于acquirequeued方法里是for(;;)

所以会继续执行shouldParkAfterFailedAcquire,此时的waitstatus=-1,返回true。

这个时候就是所谓的自旋,自旋了两次。

返回true之后根据上面代码的逻辑会走parkandcheckinterrupt方法

 

 

 调用park方法,此时t2开始阻塞。知道t1释放锁。

此时t3如果也进入lock方法。

如果t1还没有释放锁, 和t2同样的逻辑。直到t3也阻塞。

如果此时t1已经释放锁,那么会是t2拿到锁。因为入队之后,会尝试一次加锁许可的过程中,会判断当前节点是不是第一个排队的节点(也就是他的上一个节点是不是头部)

如果是才有资格去获取锁,因为前面还有t2,所以t3会继续排队,t2被唤醒之后,会将自己节点的thread属性赋值null,next指向t3,t3的pre指向t2。

然后把头部指向t2,尾部指向t3。空节点的next=null。

此时最先初始化的那个空节点已经没有任何引用。等待被回收。源码里有体现。

非公平锁

 

 调用lock方法,直接就就行cas操作。

unlock解锁过程

 

 

 

 

 

 

 

 

 简单几步:1、将status设置为0

2、设置持有当前锁的线程为null

 

3、唤醒队列里waitstatus不等于0的节点,调用unpark方法。

 

 

 简单总结:调用lock方法,存在竞争的时候,T2会去入队,首先会初始化一个空节点,t2节点实际上存放的是第二个位置,t3进来的时候继续在后面排队,

 t2和t3都是调用park方法进行阻塞。入队的时候会将前面的节点的waitstatus状态由0改为-1。在调用unlock的时候会将waitstatus不等于0的释放。

 

posted @ 2019-11-23 12:35  土豆Z120553  阅读(1127)  评论(0编辑  收藏  举报