美文网首页程序员
Java AbstractQueuedSynchronizer源

Java AbstractQueuedSynchronizer源

作者: 天不沽 | 来源:发表于2017-04-01 16:31 被阅读121次

    这篇文章说是对AbstractQueuedSynchronizer源码的阅读,倒不如说是对java interrupt的理解。

    在看await()和awaitInterruptibly()的代码前,我们先来了解下java的中断机制。

    java中断机制

    本文对中断机制的理解参考了这篇文章详细分析Java中断机制

    1. 为啥有Interrupt这个东西?
      因为存在这么个需求:一个线程去中断另一个线程。

    2. Interrupt是如何工作的?
      每个线程有一个中断标识,想要中断这个线程,可以将该线程的中断标识设置为true。该线程会在适当时机检查自己的中断标识,并决定如何处理中断。
      这种中断不具有强制性,应该属于软中断?

    java里和中断有关的三个方法如下:

    方法 作用
    boolean interrupted() 测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)
    boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响
    void interrupt() 中断线程

    await()和awaitUninterruptibly()里的中断处理

    那么,啥时候需要处理中断?捕获到中断后,又该做些什么呢?
    本文就通过await()和awaitUninterruptibly()的代码,来理解一下这里提出的两个问题。

    捕获到中断后该做些什么,是要取决于用户具体的需求的。但是,有一个基本的原则是,除非是刻意为之,否则不要将中断随随便便吞掉了。

    awaitUninterruptibly()和await()的代码如下:
    <pre>
    //没有抛出异常
    public final void awaitUninterruptibly() {
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    boolean interrupted = false;
    while (!isOnSyncQueue(node)) {
    LockSupport.park(this);
    if (Thread.interrupted())
    interrupted = true; //记录中断标识
    }
    //看是否要将中断标识再次设置为true
    if (acquireQueued(node, savedState) || interrupted)
    selfInterrupt();
    }
    </pre>
    <pre>
    public final void await() throws InterruptedException {
    if (Thread.interrupted())
    throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
    LockSupport.park(this);
    //设置InterruptMode
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    break;
    }
    //根据InterruptMode进行不同处理
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();
    if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);
    }
    </pre>

    比较这两个await接口,你会发现它们都是在LockSupport.park(this)将线程挂起之后,才开始涉及到中断的处理。(这里忽略了await()刚开始的那个中断处理)

    awaitUninterruptibly()

    仅仅是保留了下中断标识(并不关心true还是false),没有做什么特别的事情,用户基本感知不到啥。

    不知为何awaitUninterruptibly()特意处理了一下中断,其实不处理的话,中断标识本来就一直保留着的。

    这里yy一下,awaitUninterruptibly()处理中断或许是为了区分开这两种情况:

    1. 自己调用LockSupport.park()导致线程挂起,在挂起期间发生的中断;
    2. acquireQueued()调用LockSupport.park()导致线程挂起,在挂起期间发生的中断。

    从代码中可以看到,awaitUninterruptibly()在最后调用了acquireQueued(),acquireQueued()的返回值表示在acquireQueued()处理的过程中是否被中断过。awaitUninterruptibly()在线程挂起恢复之后,清除了当前线程的中断标识,并用一个局部变量interrupted重新记录了中断标识。
    这样,acquireQueued()在判断是否有中断的时候,就不会受到之前中断的影响了,而确确实实是在判断acquireQueued()这个方法本身是否有中断发生过。
    不过,即使如此,awaitUninterruptibly()中的中断处理看起来仍是无甚鸟用。

    await()

    await()与awaitUninterruptibly()最明显的区别就是抛出了InterruptedException异常。
    要说awaitUninterruptibly()是用户在无需顾及中断的时候使用,那么await()就是在用户想要程序能够及时响应中断时使用。

    await()在什么情况下抛出异常
    不是有中断就一定会抛出异常,await()在什么情况下才会抛出InterruptedException呢?

    await()中有个interruptMode,有三个值:

    1. THROW_IE:抛出异常
    2. REINTERRUPT:重置了中断标识,不抛异常
    3. 0:未发生中断,啥都不干

    是THROW_IE还是REINTERRUPT,是由checkInterruptWhileWaiting()返回的。
    这个接口的实现很简洁
    <pre>
    return Thread.interrupted() ?
    (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
    0;
    </pre>
    注释里给的说明也很清晰:
    Checks for interrupt, returning THROW_IE if interrupted before signalled, REINTERRUPT if after signalled, or 0 if not interrupted.

    意思就是:

    1. 没有中断,interruptMode = 0
    2. 中断发生在signal之前,interruptMode = THROW_IE
    3. 中断发生在signal之后,interruptMode = REINTERRUPT

    为啥扯到了singal

    await()和signal()是捉对的,await()挂起线程,signal()则是唤醒线程。

    1. 如果用户已经调用了signal,线程的唤醒是用户期望中的行为,主导权已经回到用户手中。此时,await()就不用急着根据中断来做什么及时响应了。所以,await()顶多是设置一下中断标识。
    2. 如果用户未调用signal,而线程却被唤醒了呢?
      这或许不是用户想要的(线程是因为何种异常,在用户未知的情况下被唤醒了呢?没想出来。。。),这个时候,await()就要抛出异常以通知用户。
      也或许就是用户在已经考虑到的异常中,主动将线程Interrupt了,那么当然,await()也要抛出异常通知用户。

    Interrupt: When&How

    When

    那么,到底什么时候需要处理中断呢?其实,这里应该换一个更确切的说法,那就是什么时候,我们需要中断的支持呢?

    其中的一种情况就是程序被长时间挂起的时候。
    比如说本文的LockSupoort.park(this),它响应中断,但不抛出异常(两个await方法也是基于该方法实现对中断的响应的)。
    再比如说Thread.sleep(),它响应中断,并且抛出异常。
    下面举一个实际运用到Thread.sleep()来响应中断的例子。
    <pre>
    while(run) {
    //do something
    try {
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    //do something
    }
    //clean
    }
    </pre>
    比如一个子线程实现了一个类似上面的定时任务,父线程通过设置run为false,来和谐的通知子线程退出。但是假如父线程在设置run为false时,子线程正好在sleep呢?你不想等个10秒该怎么办?你可以用interrupt打断睡眠。
    这个interrupt就好像是sleep()中又为你做了另一个小小的定时任务,一个检查是否要中断线程的小小的定时任务(不过实际上,也正是如此吧)。

    How

    那捕获到中断后要做些什么呢?
    这跟用户的需求是相关的。
    再重复一下awaitUninterruptibly()和await()。

    1. 用户想在中断后,程序还能够没事一样照常运行,为此实现了awaitUninterruptibly(),它只是默默的传递了下中断标识;
    2. 用户想要针对中断的情况进行特殊的处理,因此await()抛出了中断异常让用户来捕获。

    补充

    主要内容说完了,还有一个地方想叨逼一下。
    await()刚开始的那个中断处理,大约是为了性能考虑,使得该接口能够及时响应中断,尽量避免被挂起。


    PS:在琢磨awaitUninterruptibly()为啥特意处理了下中断,并扯到acquireQueued()的时候,我突然想起一件事情。
    小时候语文老师在给我们解析课文的时候,总会说,这一句表达了作者的爱国/愤懑/哀思等等等等,在写这一句的时候,作者脑海里是在想着这个那个。
    我就嘀咕啊,你特么怎么知道作者在想啥?作者早就驾鹤西去了,死无对证,由的你们去说啊!作者要是活过来,听到你们在这叨逼叨,说不定啼笑皆非。
    当我自己在看代码的时候,我发现自己在犯同样的毛病,我也会去琢磨:作者为何要这样写?
    虽然说文学更加抽象,但是,各行各业瞎捉摸的心情,估计是一样的吧。

    相关文章

      网友评论

        本文标题:Java AbstractQueuedSynchronizer源

        本文链接:https://www.haomeiwen.com/subject/gbuiottx.html