【Java进阶营】Java源码分析-带你认识什么是AQS(下)

前提回顾

AQS三部曲之下篇,本篇文章主要面向与对于AQS各个组件的实现的方式和原理。

源码分析

承接上一篇的结尾,进行分析相关的AQS的源码。

Node节点

首先我们来看看Node节点的实现到底是怎么回事,源码如下:

static final class Node{

/** 共享模式 */

static final Node SHARED =new Node();

/** 独占模式 */

static final Node EXCLUSIVE =null;

/**

  • 因为超时或者是中断,节点会处于删除状态,
  • 处于删除状态的节点不会转变成其他
  • 状态会一直处于删除状态
    */
    static final int CANCELLED =1;

/** 等待状态,用于表示后继节点是否需要被唤醒 */

static final int SIGNAL = -1;

/**

  • 节点再等待队列中,节点的线程将会处于Condition等待状态,只有其他线程调用了
  • Condition的signal()后,该节点由等待队列进入到同步队列,尝试获取同步状态。
    */

static final int CONDITION = -2;

/**

  • 表示下一次共享式同步状态获取将会无条件传播下去
    */

static final int PROPAGATE = -3;

/**

  • 后一个节点的等待状态:
  • SIGNAL:表示这个节点的后继节点被阻塞,到时需要通知它。
  • CANCELLED:由于中断和超时,这个节点处于被删除的状态。处于被删除状态的节
    点不会转
  • 换成其他状态,这个节点将被踢出同步队列,被GC回收。
  • CONDITION: 表示这个节点再条件队列中,因为等待某个条件而被阻塞。
  • PROPAGATE: 使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取
    可以无条件传播.
  • 0: 新节点会处于这种状态。

*/

volatile int waitStatus;

/**

  • 队列中,节点的前一个节点
    */

volatile Node prev;

/**

  • 节点的后继节点.
    */

    volatile Node next;

/**

  • 节点所拥有的线程.
    */

    volatileThread thread;

/**

  • 条件队列中,节点的下一个等待节点.
    */
    Node nextWaiter;

/**

  • 判断节点时候是共享模式.
    */

    final boolean isShared(){
    return nextWaiter == SHARED;
    }

/**

  • 获取前一个节点
    */
    final Node predecessor()throwsNullPointerException{
    Node p = prev;
    if(p ==null)
    throw new NullPointerException();
    else
    return p;
    }
    // Used to establish initial head or SHARED marker
    Node() {
    }

    // Used by addWaiter
    Node(Thread thread, Node mode) {
    this.nextWaiter = mode;
    this.thread = thread;
    }

    // Used by Condition
    Node(Thread thread,int waitStatus) {
    this.waitStatus = waitStatus;
    this.thread = thread;
    }
    }

//这个方法是自旋获取锁/获取不到锁线程挂起等待唤醒
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);
// help GC
p.next = null;
failed = false;
return interrupted;
}
//分析这个if语句 第一个条件把node的前驱节点的waitStatus置为-1 也就是signal状态
//如果这一步成功了 那么会进行parkAndCheckInterrupt方法的判断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

/**
* Convenience method to park and then check if interrupted
 * @return {@code true} if interrupted
* //可以看出这个方法就是让线程挂起,然后线程唤醒之后会返回线程的中断状态 和 
   前面的if语句结合     
   shouldParkAfterFailedAcquire(p, node) 
   parkAndCheckInterrupt() 都返回true会把interrupted置为true 然后还是会抢锁
* //返回最外层看看
*/
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

//lock方法的外层
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 可以看到 tryAcquire 和 acquireQueued 也就是抢锁失败 然后线程挂起, 唤醒后如果
// 断了还是会抢锁,最后 会调用interrupt()方法
static void selfInterrupt() {
Thread.currentThread().interrupt();
}

// await方法的外层
if( acquireQueued(node, savedState) && interruptMode !=THROW_IE)
interruptMode =REINTERRUPT;
// clean up if cancelled
if(node.nextWaiter !=null )
unlinkCancelledWaiters();
if(interruptMode !=0)
reportInterruptAfterWait(interruptMode);
可以看到 第一个if语句,如果中断而且不是signal之前中断

interruptMode =REINTERRUPT;

最后一个if语句会处理中断,抛出中断异常或者调用interrupt方法

到这里可以看到即使中断了 还是会继续抢锁,别急往下看 看另外一个lock方法

public void lockInterruptibly() throws InterruptedException {

    sync.acquireInterruptibly(1);

}

//有了之前的介绍 这个方法几步多说了

public final void acquireInterruptibly(int arg)

        throws InterruptedException {

    if (Thread.interrupted())

        throw new InterruptedException();

    if (!tryAcquire(arg))

        doAcquireInterruptibly(arg);

}

/**

* Acquires in exclusive interruptible mode.

 * @param arg the acquire argument

*            可以看到 shouldParkAfterFailedAcquire和parkAndCheckInterrupt都是true的话 会直接抛出InterruptedException异常

*            然后会在finally里cancelAcquire方法

*            而且外层也不会处理 直接抛出 那么

*/

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) &&

                    parkAndCheckInterrupt())

                throw new InterruptedException();

        }

    } finally {

        if (failed)

            cancelAcquire(node);

    }

}

/**

* Cancels an ongoing attempt to acquire.
  • @param node the node

    • 这个就是如何取消队列本尊了

    */

    private void cancelAcquire(Node node) {

      // Ignore if node doesn't exist
    
      if (node == null)
    
          return;
    
      node.thread = null;
    
      // Skip cancelled predecessors
    
      //跳过取消的节点 一直往前找waitStatus<=0的节点
    
      Node pred = node.prev;
    
      while (pred.waitStatus > 0)
    
          node.prev = pred = pred.prev;
    
      // predNext is the apparent node to unsplice. CASes below will
    
      // fail if not, in which case, we lost race vs another cancel
    
      // or signal, so no further action is necessary.
    
      Node predNext = pred.next;
    
      // Can use unconditional write instead of CAS here.
    
      // After this atomic step, other Nodes can skip past us.
    
      // Before, we are free of interference from other threads.
    
      node.waitStatus = Node.CANCELLED;
    
      // If we are the tail, remove ourselves.
    
      //如果node是末尾节点  而且设置node的前驱节点为等待队列的末尾节点成功 那就设置 node的前驱节点的下一个节点为null
    
      if (node == tail && compareAndSetTail(node, pred)) {
    
          compareAndSetNext(pred, predNext, null);
    
      } else {
    
          // If successor needs signal, try to set pred's next-link
    
          // so it will get one. Otherwise wake it up to propagate.
    
          //如果node的pred不是head 而且perd的waitStatus能设置为signal 那么就把pred.next = node.next也就是 把node去掉
    
          int ws;
    
          if (pred != head &&
    
                  ((ws = pred.waitStatus) == Node.SIGNAL ||
    
                          (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
    
                  pred.thread != null) {
    
              Node next = node.next;
    
              if (next != null && next.waitStatus <= 0)
    
                  compareAndSetNext(pred, predNext, next);
    
          } else {
    
              unparkSuccessor(node);
    
          }
    
          node.next = node; // help GC
    
      }
    

    }

下面来介绍一下线程中断

线程中断不是类似linux里面的kil -9pid ,也就是说线程中断了,就立即停止了.中断代表的是线程的状态(还有其他含义),

初始值是false

Thread实例的方法

//获取 线程的中断状态 不清楚

public boolean isInterrupted() {

    return isInterrupted(false);

}

/**

* Tests if some Thread has been interrupted.  The interrupted state

* is reset or not based on the value of ClearInterrupted that is

* passed.

* 看解释  如果参数为true就会清楚线程的中断状态 也就是重置为false

*/

private native boolean isInterrupted(boolean ClearInterrupted);

用于设置一个线程的中断状态为 true

public void interrupt() {

}

// Thread 中的静态方法,检测调用这个方法的线程是否已经中断

// 注意:这个方法返回中断状态的同时,会将此线程的中断状态重置为 false。

// 所以,如果我们连续调用两次这个方法的话,第二次的返回值肯定就是 false 了

public static boolean interrupted() {

}

对于一些方法 线程中断了

可以感知到 例如LockSupport.park(this);

线程挂起后 如果调用xxthread.interrupt()方法,线程会被唤醒 然后响不响应中断是自己的事

例如上面的两种lock方法 一种响应抛出InterruptedException异常, 另外一种只是位置线程的中断状态

列举一些能感知线程被中断的方法

来自 Object类的 wait()、wait(long)、 wait(long, int),来自 Thread类的 join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)

这几个方法的相同之处是,方法上都有:throws InterruptedException

  2.如果线程阻塞在这些方法上(我们知道,这些方法会让当前线程阻塞),这个时候如果其他线程对这个线程进行了中断,那么这个线程会从这些方法中立即返回,

抛出 InterruptedException

异常,同时重置中断状态为 false。

实现了 InterruptibleChannel

接口的类中的一些 I/O 阻塞操作,如 DatagramChannel中的 connect方法和 receive方法等

如果线程阻塞在这里,中断线程会导致这些方法抛出 ClosedByInterruptException并重置中断状态。

3.Selector 中的select 方法, 这个有机会我们在讲 NIO的时候说一旦中断,方法立即返回

对于以上 3种情况是最特殊的,因为他们能自动感知到中断(这里说自动,当然也是基于底层实现),并且在做出相应的操作后都会重置中断状态为 false。

InterruptedException 概述

它是一个特殊的异常,

不是说 JVM

对其有特殊的处理,而是它的使用场景比较特殊。通常,我们可以看到,

像 Object中的 wait() 方法,ReentrantLock 中的lockInterruptibly() 方法,

Thread 中的sleep() 方法等等,这些方法都带有 throws InterruptedException,我们通常称这些方法为阻塞方法(

blocking method)。

阻塞方法一个很明显的特征是,它们需要花费比较长的时间(不是绝对的,只是说明时间不可控),还有它们的方法结束返回往往依赖于外部条件,

如 wait

方法依赖于其他线程的 notify,

lock 方法依赖于其他线程的

unlock等等。

当我们看到方法上带有 throws

InterruptedException 时,我们就要知道,这个方法应该是阻塞方法,我们如果希望它能早点返回的话,我们往往可以通过中断来实现。

除了几个特殊类(

如 Object,Thread等)外,感知中断并提前返回是通过轮询中断状态来实现的。我们自己需要写可中断的方法的时候,就是通过在合适的时机(通常在循环的开始处)去判断线程的中断状态,

     然后做相应的操作(通常是方法直接返回或者抛出异常)。当然,我们也要看到,如果我们一次循环花的时间比较长的话,那么就需要比较长的时间才能注意到线程中断了。

怎么处理中断 可以参考AQS怎么做的

lock方法 这个不方法不响应中断但是也没有吧中断信息给丢掉

在最外层 还是把线程的中断状态给设置为true

自己写代码时可以自行检测

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

    }

static void selfInterrupt() {

    Thread.currentThread().interrupt();

}

lockInterruptibly 方法

上面介绍过 这里就不详细介绍了

该方法 检测到中断

直接抛出中断异常 而且不做处理

向外层抛出

在java的并发包中 对于中断的响应都是分为两类

响应不响应,不响应也不会把中断信息丢掉,记录状态
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值