【AQS】独占锁的释放

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

文章首发于:clawhub.club


上一次写独占锁的获取,写到当前线程被挂起之后就结束了,那么什么时候才能唤醒这个线程呢?这次来分析独占锁的释放。JAVA中的内置锁在线程退出临界区时会自动释放锁,而ReentrantLock之类的显示锁,需要手动释放,一定要写到finally代码块中!!
因为锁的释放没有公平与非公平之说,所以直接写在了AQS中:

      /**
         * Releases in exclusive mode.  Implemented by unblocking one or
         * more threads if {@link #tryRelease} returns true.
         * This method can be used to implement method {@link Lock#unlock}.
         *
         * @param arg the release argument.  This value is conveyed to
         *        {@link #tryRelease} but is otherwise uninterpreted and
         *        can represent anything you like.
         * @return the value returned from {@link #tryRelease}
         */
        public final boolean release(int arg) {
            //尝试释放锁,由子类实现
            if (tryRelease(arg)) {
                Node h = head;
                //如果头节点不为null,并且waitStatus不是最初初始化时的状态
                if (h != null && h.waitStatus != 0)
                    //唤醒下一个节点
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }

源码看起来并不复杂,主要由tryRelease(arg)和unparkSuccessor(h)实现。

tryRelease(int releases)

ReentrantLock中的Sync内部类实现了AQS接口,所以也就实现了相应的tryRelease方法,源码中没有用到CAS等技术,是因为release方法的执行一定是获取锁之后执行的,再重点提示一下,一定要在finally代码块中释放锁。

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

unparkSuccessor(Node node)

目的就是唤醒离头节点最近的节点线程,从后向前迭代,在分析节点插入时,因为不是原子操作,所以可能会有尾节点分叉现象,所以从后往前找更合适。

     /**
         * Wakes up node's successor, if one exists.
         *
         * @param node the node
         */
        private void unparkSuccessor(Node node) {
            /*
             * If status is negative (i.e., possibly needing signal) try
             * to clear in anticipation of signalling.  It is OK if this
             * fails or if status is changed by waiting thread.
             */
            int ws = node.waitStatus;
            //ws:CANCELLED=1 ,初始化时等于0,但是进入这个方法前确定ws不等于0,
            // 所以只要ws不为CANCELLED,就将ws置为0
            if (ws < 0)
                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;
            //后继节点为null或者后继节点ws为CANCELLED
            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);
        }

执行到 LockSupport.unpark(s.thread);这一步时,唤醒了那个阻塞的线程,就回到了上一篇文章中的parkAndCheckInterrupt方法中,LockSupport.park(this)被打断唤醒,所以向下执行,获取线程中断状态,并且清空中断状态。

     /**
         * Convenience method to park and then check if interrupted
         *
         * @return {@code true} if interrupted
         */
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }

之后返回上一层代码acquireQueued(final Node node, int arg):

    final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (; ; ) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                            parkAndCheckInterrupt())
                        //就看这里!!!!
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

如源码,这里保留了中断状态,继续循环,当获取锁之后,返回了中断状态,继续返回上一层代码acquire(int arg),独占锁的获取逻辑:

    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                //就看这里!!!
                selfInterrupt();
        }

如上,执行到了 selfInterrupt()方法:

       /**
         * Convenience method to interrupt current thread.
         */
        static void selfInterrupt() {
            //中断当前线程
            Thread.currentThread().interrupt();
        }

中断了当前线程,当然,只是标志一下当前线程是中断的,纵观独占锁的获取与释放可以看出,AQS只是将中断延后处理,并没有响应中断。


来源:https://www.jianshu.com/p/347ea7f881f8

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【AQS】独占锁的释放

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏