AQS——Condition源码

当一个持锁的线程调用condition的await()方法时,AQS就将该线程封装成一个节点,丢进条件队列里,同时释放锁,从同步队列里移除。当它被唤醒时,又被加到同步队列里去排队抢锁。Condition的await()、signal()、signalAll()方法分别对应着Object的wait()、notify()、notifyAll()方法。二者的区别在于一个AQS锁可以创建多个条件队列,而Object作为锁时只有一个条件队列。
condition的一个常见应用场景是阻塞队列(BlockingQueue),我们可以看个实例:

package lnstark.locks;

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

/**
 * by Lnstark
 * 2021/1/9
 */
public class MyBlockingQueue {
    private Lock lock = new ReentrantLock();

    private Condition putCond = lock.newCondition();
    private Condition takeCond = lock.newCondition();

    private T[] queue;
    private int size = 0, capacity = 0;
    private int putIdx = 0, takeIdx = 0;

    public int getSize() {
        return size;
    }

    public MyBlockingQueue(int capacity) {
        this.capacity = capacity;
        queue = (T[]) new Object[capacity];
    }

    /**
     * 生产
     */
    public void put(T t) {
        lock.lock();
        try {

            // 如果满了,生产者等待
            // 在被唤醒之后到获取锁之前可能会有其他线程先拿到锁,执行了put
            // 所以用while而不是If,下同
            while (size == capacity) {
                System.out.println(Thread.currentThread().getName() + "等待");
                putCond.await();
                System.out.println(Thread.currentThread().getName() + "唤醒");
            }
            // 将对象丢进队列,移动生产对象下标
            queue[putIdx++] = t;
            size++;
            // 判断是否到头
            if (putIdx == capacity) {
                putIdx = 0;
            }
            System.out.println(Thread.currentThread().getName() + "生产了" + t);
            takeCond.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放锁");
        }
    }

    /**
     * 消费
     */
    public T take() {
        lock.lock();
        try {
            // 如果空了,消费者等待
            while (size == 0) {
                System.out.println(Thread.currentThread().getName() + "等待");
                takeCond.await();
                System.out.println(Thread.currentThread().getName() + "唤醒");
            }
            // 将对象丢进队列,移动生产对象下标
            if (queue[takeIdx] == null) {
                System.out.println();
            }
            T t = queue[takeIdx];
            queue[takeIdx++] = null;
            size--;

            // 判断是否到头
            if (takeIdx == capacity) {
                takeIdx = 0;
            }
            putCond.signal();
            System.out.println(Thread.currentThread().getName() + "消费" + t);
            return t;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return null;
    }

    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue<>(10);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                final int fi = i;
                new Thread(() -> {
                    queue.put(fi);
                }, "producer" + i).start();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                final int fi = i;
                new Thread(() -> {
                    queue.take();
//                    System.out.println("consumer" + fi + "消费: " + queue.take());
                }, "consumer" + i).start();
            }
        });
//        queue.take();
        t1.start();
        t2.start();

    }
}

执行结果(部分截取):

consumer5等待
consumer6等待
consumer13等待
consumer9等待
consumer10等待
consumer12等待
consumer16等待
producer4生产了4
producer4释放锁
consumer17消费4
consumer8等待
consumer11等待

下面看看Condition源码部分。

一、await方法

await方法主要就是,先将当前线程封装成节点加入条件队列,然后释放锁,挂起线程。被唤醒(可能是前驱节点唤醒也可能是中断唤醒)之后就在阻塞队列里去竞争抢锁。

 public final void await() throws InterruptedException {
    // 如果发生过中断,则抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 将当前线程封装成condition节点,并加入条件队列
    Node node = addConditionWaiter();
    // 完全释放锁,并返回锁的重入次数state
    int savedState = fullyRelease(node);
    // 中断模式:
    // 0,表示未发生过中断,
    // THROW_IE,即-1,表示在条件队列内发生过中断
    // REINTERRUPT,即1,表示在同步队列内发生了中断
    int interruptMode = 0;
    // 如果不在同步队列内
    while (!isOnSyncQueue(node)) {
        // 挂起线程
        LockSupport.park(this);
        // 判断中断状态,如果发生了中断就跳出循环
        // checkInterruptWhileWaiting方法里如果在条件队列内发生了中断
        // 节点会被移到同步队列里
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 唤醒之后并且已经在同步队列里了,于是就去acquireQueued竞争抢锁
    // 如果抢到锁,就把之前的state设置回去
    // 如果acquireQueued发生中断就返回true,如果中断状态不是THROW_IE,就设置为REINTERRUPT
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 如果节点移到阻塞队列之后nextWaiter没断开
    // 就执行unlinkCancelledWaiters清除条件队列里的cancelled节点
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 如果挂起到唤醒过程里发生过中断,就执行reportInterruptAfterWait处理中断
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

我们具体看看里面的小方法,先来看addConditionWaiter():

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    // 如果尾节点是cancelled状态,则清空队列里的cancelled节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 创建节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

unlinkCancelledWaiters方法:

private void unlinkCancelledWaiters() {
    // 遍历并移除队内的cancelled节点
    // t是当前节点,trail是当前节点的前置节点
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

就是用三个指针遍历并移除cancelled节点的链表操作算法,不难看懂。
下面是释放锁fullyRelease(node)方法:

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

就是简单的释放锁,返回state,需要注意的是释放锁失败,或者当前未持锁都会抛出异常,然后将当前节点置为cancelled状态。下面看isOnSyncQueue(node),判断节点是否在同步队列里的方法:

final boolean isOnSyncQueue(Node node) {
    // 如果waitStatus是CONDITION 那肯定还在条件队列
    // node.prev == null说明可能waitStatus已经被修改,但是还没来得及修改node.prev
    // 节点正在转移到同步队列。或者他是canclled状态,那就呆在条件队列里等待被清除。
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果next不为空,那么他已经在同步队列里
    // 因为入队先设置node.prev,再CAS node为tail,最后前尾节点的next指向node
    if (node.next != null) // If has successor, it must be on queue
        return true;
    // 在条件队列里面找
    return findNodeFromTail(node);
}

findNodeFromTail方法:

private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

遍历直到找到node。
下面是checkInterruptWhileWaiting方法:

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

如果没发生中断返回0。
transferAfterCancelledWait方法判断是否在条件队列内中断:

final boolean transferAfterCancelledWait(Node node) {
    // 如果cas成功,说明状态还是CONDITION。
    // 如果已经转移至同步队列waitStatus会变成0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        // 发生中断的节点会直接被转移到同步队列
        enq(node);
        return true;
    }
    // 如果不在条件队列里说明正在转移,稍微等一会直到转移完成
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

这两个方法判断了中断情况,如果发生中断了,节点还在条件队列里的话,那就转移至同步队列。
回到awai方法,还有最后一个处理中断结果方法reportInterruptAfterWait:

private void reportInterruptAfterWait(int interruptMode)
          throws InterruptedException {
    // 如果条件队列内中断就抛出异常
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    // 否则返回中断状态
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}
二、signal方法

signal方法唤醒条件队列里的第一个不是cancelled的节点,将它转移到同步队列。

public final void signal() {
    // 如果未持锁,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        // 如果nextWaiter是null,那队列就空了,将lastWaiter置为null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        // 断开first节点
        first.nextWaiter = null;
        // 如果转移失败,那就转移下一个节点
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

transferForSignal方法:

final boolean transferForSignal(Node node) {
    // 如果失败,可能是canclled状态的节点,那么不需要转移
    // 也可能是被中断的节点,主动修改了状态,正要进入同步队列
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    // 入队,返回前置节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // 如果前置节点为cancelled状态(ws > 0),或者CAS失败(可能其他线程正在修改ws)
    // 那么就唤醒当前线程,去清除或修改前置节点为signal状态
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    // 入队成功,返回true
    return true;
}

这个看懂了signalAll自然也就看懂了,它是遍历并转移队列里每个符合条件的节点。
以上就是条件队列的await和signal的大致逻辑了。

你可能感兴趣的:(AQS——Condition源码)