公平锁
调用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的释放。