AQS中acquire()中为什么要在if中调用selfInterrupt()?

/ 默认分类 / 0 条评论 / 1385浏览

AQS中acquire()为什么要在if中调用selfInterrupt()?

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

上面是acquire方法的源码,前面我有分析过,这里主要解释一下为什么要调用selfInterrupt().

//这个方法可以理解为入队列后,尝试获取锁
    final boolean acquireQueued(final Node node, int arg) {
        //默认假设加锁就是失败了
        boolean failed = true;
        try {
            //设置当前线程的中断标识为false
            boolean interrupted = false;
            //当前节点线程会一直自旋,直到获取锁成功,期间可能会被park
            for (;;) {
                //获取当前节点的前节点
                final Node p = node.predecessor();
                //如果当前节点的前节点就是head,那么说明当前节点就是第同步阻塞队列的排在第一的线程节点
                // 那么就尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    //如果获取锁成功那么就会执行这个if,设置同步阻塞队列的头为当前节点(之前介绍了同步阻塞队列的头结点没有保存阻塞线程的作用),只是保存了下一个节点的状态
                    //所以这里设置这个已经从同步阻塞队列中获取了锁的节点,它等于已经出队列了,也就等于将之前的头结点去掉了
                    setHead(node);
                    p.next = null; // help GC
                    //设置加锁失败标志为false
                    failed = false;
                    //加锁成功后,返回中断标志(可能是中断状态,因为下面会修改)
                    return interrupted;
                }
                //如果获取锁失败,判断当前是否需要被park(其实就是判断当前节点的前一个节点的waitStatus是否被设置为SIGNAL了)
                if (shouldParkAfterFailedAcquire(p, node) &&//如果需要park,则调用park
                    parkAndCheckInterrupt())
                    //设置中断标志为true
                    interrupted = true;
            }
        } finally {
            //最终,如果自旋加锁失败了,就废弃当前节点
            if (failed)
                cancelAcquire(node);
        }
    }
    private final boolean parkAndCheckInterrupt() {
        //先park当前线程
        LockSupport.park(this);
        //返回中断标志
        return Thread.interrupted();
    }

如果当前线程是非中断状态,那么parkAndCheckInterrupt中调用park后,当前线程就会阻塞挂起,如果期间发生中断,那么park就会返回,并且 设置中断标志为true,所以parkAndCheckInterrupt返回true,但是调用Thread.interrupted()会清除中断标志,即虽然返回了中断标志true,但是 之后的中断标志还是被设置为了false. 所以如果再次进入for循环后要是获取锁成功了,那么就会返回中断标志为true,此时acquireQueued方法中的 for无限循环结束,方法返回,此时执行selfInterrupt(),那这意思就是,如果线程发生中断了,就再次调用中断?其实这里调用中断的原因是为了 补充在前面parkAndCheckInterrupt中因为调用Thread.interrupted()而被清除的中断标志.

那么问题又来了,即然只是因为之前中断标志被清除了,那么为什么不在前面直接调用Thread.currentThread.isInterrupted()呢?这样不就不会清除 标记了吗?

哈哈哈,很好,说明你很会思考,但其实这么做另有原因,导致这里不得不调用Thread.interrupted(),因为如果不清除中断标记.

那么下一次进入for循环,如果还是没有获取锁成功,并检测到需要park,那么再次进入parkAndCheckInterrupt()方法,那么就会发现调用park阻塞线程失败,因为前面我们介绍 Unsafe和LockSupport的时候说过,线程处于中断状态,那么park是会立刻返回不会发生阻塞,那这就麻烦了,当前线程就会一直陷入无限的死循环中,导致 cpu资源大量消耗.所以如果消除了中断标志,那么下一次调用park还是会阻塞,暂停循环.