Java中的AQS(三)线程的阻塞和唤醒

一、线程的阻塞:    

    在上一篇说到,线程获取同步状态失败的线程,会构造节点并加入到同步队列的尾部,然后通过自旋的方式不断的获取同步状态,但是在自旋过程中需要判断线程是否需要阻塞,我们再一次看一下acquire方法。

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);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //也就是在这里需要判断线程是否需要阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }

首先打开shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驱节点的等待状态
    int ws = pred.waitStatus;
    //当前线程处于等待状态  返回true
    if (ws == Node.SIGNAL)
        return true;
    //说明前驱节点线程等待超时或者被中断,需要将前驱节点从同步队列中移除
    if (ws > 0) {
        do {
            //从同步队列中移除前驱节点
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    //状态为Condition或者propageate
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

    从上述代码可以看出当前线程是否需要阻塞:

        1、如果当前线程节点的前驱节点为SINGAL状态,则表明当前线程处于等待状态,返回true,当前线程阻塞

        2、如果当前线程节点的前驱节点状态为CANCELLED(值为1),则表明前驱节点线程已经等待超时或者被中断,此时需要将该节点从同步队列中移除掉。最后返回false

        3、如果当前节点节点前驱节点非SINGAL,CANCELLED状态,则通过CAS将其前驱节点的等待状态设置为SINGAL,返回false。

    如果shouldParkAfterFailedAcquire方法返回true,则需要调用parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
    //阻塞当前线程
    LockSupport.park(this);
    return Thread.interrupted();
}

    parkAndCheckInterrupt方法会阻塞当前线程(使用LockSupport的park方法),并返回当前线程的中断状态。

二、线程的唤醒

    当线程释放同步状态后,将唤醒其后继节点:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

    唤醒线程使用unparkSuccessor方法

private void unparkSuccessor(Node node) {
    //头结点的等待状态
    int ws = node.waitStatus;
    //如果头结点的状态小于0(初始状态)
    if (ws < 0)
        //使用CAS操作将头结点的状态设置为0
        compareAndSetWaitStatus(node, ws, 0);
    //头结点的后继节点
    Node s = node.next;
    //当后继节点为空或者等待超时或被中断
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从同步队列尾部开始,找到一个可以唤醒的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //唤醒后继节点
    if (s != null)
        LockSupport.unpark(s.thread);
}

在唤醒后继节点时,后继节点可能为空,等待超时,被中断,这个时候将从同步队列尾部开始寻找,寻找一个可唤醒的节点,然后调用unpark来唤醒该节点的线程。

三、LockSupport

    从上面内容我们注意到,线程的阻塞和唤醒都使用到了LockSupport的方法,LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

    LockSupport定义了一组以park开头的方法用来阻塞线程,以及unpark来唤醒一个被阻塞的线程。

    LockSupport提供的方法:


    在Java6中,LockSupport增加了park(Object blocker),parkNanos(Object blocker,long nanos)和parkUtil(Object blocker,long deadline)三个方法,用于实现阻塞当前线程的功能。

    其中blocker是用来标识当前线程在等待的对象。该对象主要用于问题排查和系统监控。

park方法

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

unpark方法

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
非法内部都是使用UNSAFE来实现的。


猜你喜欢

转载自blog.csdn.net/yanghan1222/article/details/80252755