AQS 里的 doAcquireInterruptibly,如果已经阻塞在 park 了,是不可能被唤醒的吧?

2020-05-17 18:44:22 +08:00
 amiwrong123

AQS 里的 doAcquireInterruptibly 时,如果已经阻塞在 park 了,且当前持有锁的线程就是不释放锁,那阻塞在 park 的线程是不可能被唤醒的吧?

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//设置好上一个 node 的 signal 信号
                    parkAndCheckInterrupt())//然后就阻塞
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//直接阻塞的,只有唤醒后才知道当前线程有没有中断过
        return Thread.interrupted();
    }

从上面代码的注释处,可以看出,当同步器的锁已经被别的线程获得,且当前线程没有中断过时,当前线程执行 doAcquireInterruptibly 的流程是:addWaiter 加入当前线程的 node,然后 shouldParkAfterFailedAcquire 设置一下上一个 node 的 signal 信号,然后就 parkAndCheckInterrupt 阻塞了,也就是说,刚开始这 for 循环只执行一次 就阻塞了。

根据上面的论据,使用下面例子:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class test3 {
    public static void main(String[] args) throws InterruptedException {
        final Lock lock=new ReentrantLock();
        lock.lock();

        Thread t1=new Thread(new Runnable(){
            @Override
            public void run() {
	        	try {
					lock.lockInterruptibly();
				} catch (InterruptedException e) {
					e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()+" interrupted.");
				}
                System.out.println(Thread.currentThread().getName()+"结束了");
            }
        });

        t1.start();
        Thread.currentThread().sleep(5000);
        t1.interrupt();
        while (true){}
    }
}
打印结果:
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at test3$1.run(test3.java:19)
	at java.lang.Thread.run(Thread.java:745)
Thread-0 interrupted.
Thread-0 结束了

所以为什么上面这个例子,最后还是抛出了异常?因为都没有人调用unpark(子线程)

1747 次点击
所在节点    Java
4 条回复
hfc
2020-05-18 10:33:33 +08:00
.interrupt()也能使线程从.park()引起的线程等待状态中恢复
amiwrong123
2020-05-18 19:06:52 +08:00
@hfc
是嘛,在 interrupt 的源码里能看到有调用 unpark 的吗
hfc
2020-05-19 09:15:34 +08:00
@amiwrong123 不行,是 native 本地方法了,但是从其注释中应该可以略知一二
hanshijun
2020-12-05 18:18:44 +08:00
t1 调用 interrupt,会使 park 方法返回并将线程中的中断标记位置为 true,所以 parkAndCheckInterrupt()返回 true,调用了 throw new InterruptedException(); 因此抛出了异常

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/672635

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX