java并发包-ReentrantLock(三):如何解锁

(本文源代码来自openjdk1.8,有错误和不足欢迎讨论指正)
看了前面讲述lock工作原理的文章,我们对ReentrantLock的实现方式有了了解。ReentrantLock中由其内部对象sync中的state成员记录其锁状态,当state=0时,代表无线程占用锁,state=n,n代表同一线程对锁的重入次数。lock的逻辑无疑就是尝试将state值从零改为1(占用)或者增加1(重入),否则排队阻塞至实现此工作。
那么我们不难推断unlock方法的逻辑,unlock方法不必考虑线程竞争的问题(因为ReentrantLock是排它锁,同一时间只有一个线程占有锁,因此只能有一个线程unlock),unlock应当会将state值减1,如果state值归0了,代表此线程已经完全释放了该锁,此时唤醒其后继节点参与竞争锁。查看其源代码,逻辑也确实如此。
当我们离开同步代码块的时候,需要释放掉持有的锁,往往在finally块中执行此逻辑以保证代码无论以何种方式结束都能释放锁。同样的,当我们调用unlcok方法时,ReentrantLock也是将此操作代理给了内部对象sync:

   public void unlock() {
        sync.release(1);//释放一个锁次数(因为可重入)
    }

同样的,release进行释放锁的逻辑,他先调用tryRelease,可以看到tryRelease方法的返回值影响到了是否唤醒后继节点,因此在此我们可以推测tryRelease方法应当在完全释放锁的时候返回true:

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的实现如下,确是如同推测,当c==0时,返回true,此时锁被完全释放,进行唤醒后继节点操作:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//减去释放次数
            //如果释放锁的线程不是当前线程,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())//
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//锁完全被释放
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

回到release方法,如果tryRelease返回true,那么进行后继节点的唤醒,执行unparkSuccessor,可能会有一个疑问是:非公平的锁抢占成功时,那么它的后继节点怎么是谁呢?其实,非公平的线程并没有修改任何队列信息,也就是没有入队,因此此时的队列头仍然是上一个释放锁的线程,这时候非公平的锁相当于唤醒了被它抢了机会的那个倒霉蛋。

private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitState(node, ws, 0);//后继节点被唤醒,signal置为0
        Node s = node.next;//查找到后继节点
        if (s == null || s.waitStatus > 0) {//如果当前没有后继节点后者后继节点任务已经取消
            s = null;
//从尾巴开始寻找,找到最靠前的非取消节点
//不能从当前后继节点开始寻找,因为当一个节点被取消时,可能有其他线程已经把它的next置为了null,或者指向一个无用值以help gc。比如在cancelAcquire中有这样一段
//node.next=node.被取消的node的next引用指向了自己.
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)//找到了合适的节点,为它发放许可
            LockSupport.unpark(s.thread);   
    }

这样队列头的后继线程就被发放了许可,可以进行锁的竞争了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值