锁机制(四)

本小节介绍锁释放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);
    }

公平锁和非公平锁只是在获取锁的时候有差别,其它都是一样的。

在上面非公平锁的代码中总是优先尝试当前是否有线程持有锁,一旦没有任何线程持有锁,那么非公平锁就霸道的尝试将锁“占为己有”。如果在抢占锁的时候失败就和公平锁一样老老实实的去排队。

也即是说公平锁和非公平锁只是在入AQSCLH队列之前有所差别,一旦进入了队列,所有线程都是按照队列中先来后到的顺序请求锁。

 

posted @ 2017-08-05 15:54  jerrice  阅读(205)  评论(0编辑  收藏  举报