《Java源码分析》:ReentrantLock.unlock 释放锁

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等

ReentrantLock.unlock()

API给出的介绍为:

试图释放此锁。
如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁。如果当前线程不是此锁的持有者,则抛出 IllegalMonitorStateException。

下面就从源码的角度来分析如何释放锁。

ReentrantLock.lock方法如下,直接调用了AQS类中的release方法。

        public void unlock() {
            sync.release(1);//直接调用release方法
        }

AQS类中的release方法的代码如下:

        /*
            函数功能:以独占的方式释放,如果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释放锁成功,返回true。则调用unparkSuccessor函数来将头结点的继任节点唤醒。

下面我们来具体看release(int arg)方法中的tryRelease(int arg)和 unparkSuccessor(Node node).

tryRelease(int arg)

对于独占锁,函数tryRelease的参数arg == 1.即只有一个线程可能拥有锁。其它线程处于阻塞中。

        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;
        }

代码分析

1、先检查当前线程是不是拥有锁的独占线程。如果不是,则抛异常。这是因为线程不能够释放其它线程所拥有的锁。如果是,则进行 2.

2、将AQS状态位减少要释放的次数(对于独占锁次数为1)。如果剩余的状态位为 0(也就是没有线程持有锁).那么当前线程是最后一个持有锁的线程,调用setExclusiveOwnerThread来设置AQS持有锁的独占线程为null.接着进行 3

3、将剩余的状态位写会AQS,如果没有线程持有锁,则返回true,否则返回false

这里c==0决定了是否完全释放了锁。由于ReentrantLock是可重入锁,因此同一个线程可能多重持有锁,那么当且仅当最后一个持有锁的线程释放锁是才能将AQS中持有锁的独占线程清空,这样接下来的操作才需要唤醒下一个需要锁的AQS节点(Node),否则就只是减少锁持有的计数器,并不能改变其他操作。

当tryRelease操作成功后(也就是完全释放了锁),release操作才能检查是否需要唤醒下一个继任节点。这里的前提是AQS队列的头结点需要锁(waitStatus!=0),如果头结点需要锁,就开始检测下一个继任节点是否需要锁操作。

我们在上篇博文中知道acquireQueued操作完成后(拿到了锁),会将当前持有锁的节点设为头结点,所以一旦头结点释放锁,那么就需要寻找头结点的下一个需要锁的继任节点,并唤醒它。

unparkSuccessor(Node node)方法

方法的源码如下,

函数功能:找到当前节点的下一个需要锁的继任节点,并将调用unpark将其唤醒。

        private void unparkSuccessor(Node node) {
            //此时node是需要是需要释放锁的头结点
            int ws = node.waitStatus;
            if (ws < 0)//清空头结点的waitStatus,也就是不再需要锁了
                compareAndSetWaitStatus(node, ws, 0);
    
            /*
             * Thread to unpark is held in successor, which is normally
             * just the next node.  But if cancelled or apparently null,
             * traverse backwards from tail to find the actual
             * non-cancelled successor.
             */
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {
                s = null;
                //从队列的后面开始寻找第最后一个waitStatus<=0的节点,然后唤醒,为什么不是从前往后,而是从后往前,不知道。
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            //如果找到一个有效的继任节点,就唤醒此节点线程
            if (s != null)
                LockSupport.unpark(s.thread);
        }
    
        //LockSupport类中的unpark方法如下
        public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }

上面代码的思路比较简单。就是找出头结点的下一个需要锁的继任节点然后唤醒。

为什么在unparkSuccessor()函数中是
从队列的后面开始寻找第最后一个waitStatus<=0的节点,然后唤醒,为什么不是从前往后,而是从后往前??

这个目前还没有找到答案

以上就是释放锁的全部过程。

小结

unlock释放锁的大致思想:如果当前线程释放锁成功,则寻找头结点的下一个需要锁的继任节点并唤醒。如果释放锁不成功,则什么也不做。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值