zoukankan      html  css  js  c++  java
  • 锁机制(四)

    本小节介绍锁释放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队列之前有所差别,一旦进入了队列,所有线程都是按照队列中先来后到的顺序请求锁。

  • 相关阅读:
    推荐两个在线格式化JavaScript代码的网站
    JavaScript 获取未知对象的属性
    笔记本“电源已接通,未充电”的解决办法
    一个好的网站: StudioStyles
    Vertical Text with CSS(用CSS竖向排列文本)
    TSQL 行列互换
    获得enum中的枚举值
    恶搞百度
    想买个洗衣机,吓出了一身水……
    一个获取SQL Server数据库连接字符串的简单方法
  • 原文地址:https://www.cnblogs.com/jerrice/p/7290483.html
Copyright © 2011-2022 走看看