首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

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

  •  
  •   amiwrong123 · 55 天前 · 506 次点击
    这是一个创建于 55 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 结束了
    
    • 首先主线程获得了锁,且最后执行死循环,所以永远不释放锁。
    • 在主线程睡觉期间,子线程已经阻塞在 parkAndCheckInterrupt 里了。
    • 等主线程睡醒了,设置子线程的中断状态。但主线程永远不释放锁,那永远不会有人调用unpark(子线程),那子线程岂不是应该永远阻塞吗?

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

    3 条回复    2020-05-19 09:15:34 +08:00
    hfc
        1
    hfc   55 天前
    .interrupt()也能使线程从.park()引起的线程等待状态中恢复
    amiwrong123
        2
    amiwrong123   54 天前 via Android
    @hfc
    是嘛,在 interrupt 的源码里能看到有调用 unpark 的吗
    hfc
        3
    hfc   54 天前
    @amiwrong123 不行,是 native 本地方法了,但是从其注释中应该可以略知一二
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2535 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 03:52 · PVG 11:52 · LAX 20:52 · JFK 23:52
    ♥ Do have faith in what you're doing.