首页 > 代码库 > 锁机制(四)
锁机制(四)
本小节介绍锁释放Lock.unlock()。
Release/TryRelease
unlock操作实际上就调用了AQS的release操作,释放持有的锁。
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
前面提到过tryRelease(arg)操作,此操作里面总是尝试去释放锁,如果成功,说明锁确实被当前线程持有,那么就看AQS队列中的头结点是否为空并且能否被唤醒,如果可以的话就唤醒继任节点(下一个非CANCELLED节点,下面会具体分析)。
对独占锁,java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int) 释放锁的过程。
protected final boolean tryRelease(int releases) { int c = getState() - releases;//将AQS状态位减少要释放的次数(对于独占锁而言总是1 if (Thread.currentThread() != getExclusiveOwnerThread())//判断持有锁的线程是否是当前线程 throw new IllegalMonitorStateException();//如果不是就抛出IllegalMonitorStateExeception() boolean free = false; if (c == 0) { //如果剩余的状态位0(也就是没有线程持有锁) free = true;// setExclusiveOwnerThread(null);//当前线程是最后一个持有锁的线程,清空AQS持有锁的独占线程 } setState(c);//将剩余的状态位写回AQS,如果没有线程持有锁就返回true,否则就是false return free; }
参考上一节的分析就可以知道,这里c==0决定了是否完全释放了锁。由于ReentrantLock是可重入锁,因此同一个线程可能多重持有锁,那么当且仅当最后一个持有锁的线程释放锁是才能将AQS中持有锁的独占线程清空,这样接下来的操作才需要唤醒下一个需要锁的AQS节点(Node),否则就只是减少锁持有的计数器,并不能改变其他操作。
当tryRelease操作成功后(也就是完全释放了锁),release操作才能检查是否需要唤醒下一个继任节点。这里的前提是AQS队列的头结点需要锁(waitStatus!=0),如果头结点需要锁,就开始检测下一个继任节点是否需要锁操作。
在上一节中说道acquireQueued操作完成后(拿到了锁),会将当前持有锁的节点设为头结点,所以一旦头结点释放锁,那么就需要寻找头结点的下一个需要锁的继任节点,并唤醒它。
private void unparkSuccessor(Node node) { //此时node是需要是需要释放锁的头结点 //清空头结点的waitStatus,也就是不再需要锁了 compareAndSetWaitStatus(node, Node.SIGNAL, 0); //从头结点的下一个节点开始寻找继任节点,当且仅当继任节点的waitStatus<=0才是有效继任节点,否则将这些waitStatus>0(也就是CANCELLED的节点)从AQS队列中剔除 //这里并没有从head->tail开始寻找,而是从tail->head寻找最后一个有效节点。 //解释在这里 http://www.blogjava.net/xylz/archive/2010/07/08/325540.html#377512 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } //如果找到一个有效的继任节点,就唤醒此节点线程 if (s != null) LockSupport.unpark(s.thread); }
公平锁和非公平锁只是在获取锁的时候有差别,其它都是一样的。
在上面非公平锁的代码中总是优先尝试当前是否有线程持有锁,一旦没有任何线程持有锁,那么非公平锁就霸道的尝试将锁“占为己有”。如果在抢占锁的时候失败就和公平锁一样老老实实的去排队。
也即是说公平锁和非公平锁只是在入AQS的CLH队列之前有所差别,一旦进入了队列,所有线程都是按照队列中先来后到的顺序请求锁。
锁机制(四)