AQS源码分析之ConditionObject

来源:互联网 发布:centos 7 伪静态 编辑:程序博客网 时间:2024/06/05 20:22

简介

传统的内置锁都只能有一个相关联的条件队列,因此多个线程可能在同一个条件队列上等待不同的条件谓词,导致使用notify时导致信号消失,或者使用notifyAll唤醒了非等待该信号类型的线程,造成了极大的开销。

因此,可以使用显式的Lock和Condition而不是内置锁和条件队列,来编写一个带有多个条件谓词的并发对象。在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于限时的等待、以及公平的或非公平的队列操作。

与内置条件队列不同的是,对于每个Lock,可以由任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会按照FIFO顺序从Condition.await中释放。

public class ConditionBoundedBuffer<T> {    protected final Lock lock = new ReentrantLock();    private final Condition notFull = lock.newCondition();    private final Condition notEmpty = lock.newCondition();    @SuppressWarnings("unchecked")    private final T[] items = (T[]) new Object[19];    private int tail, head, count;    public void put(T x) throws InterruptedException {        lock.lock();        try {            while (count == items.length) {                notFull.await();            }            items[tail] = x;            if (++tail == items.length) {                tail = 0;            }            ++count;            notEmpty.signal();        } finally {            lock.unlock();        }    }    public T take() throws InterruptedException {        lock.lock();        try {            while (count == 0) {                notEmpty.await();            }            T x = items[head];            items[head] = null;            if (++head == items.length) {                head = 0;            }            notFull.signal();            return x;        } finally {            lock.unlock();        }    }}

通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求,signal比signalAll更高效,能够极大减少在每次缓存操作中发生的上下文切换与锁请求的次数。

与内置锁和条件队列一样,但使用显式的Lock和Condition时,也必须满足锁、条件谓词和条件变量之间的三元关系。在条件谓词中包含的变量必须由Lock来保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象。

Condition接口

public interface Condition {     /**       * 暂停此线程直至一下四种情况发生      * 1.此Condition被signal()      * 2.此Condition被signalAll()      * 3.Thread.interrupt()      * 4.伪wakeup      * 以上情况.在能恢复方法执行时,当前线程必须要能获得锁      */    void await() throws InterruptedException;    //跟上面类似,不过不响应中断    void awaitUninterruptibly();    //带超时时间的await(),并响应中断    long awaitNanos(long nanosTimeout) throws InterruptedException;    //带超时时间的await()    boolean await(long time, TimeUnit unit) throws InterruptedException;    //带deadline的await()    boolean awaitUntil(Date deadline) throws InterruptedException;    //唤醒某个等待在此condition的线程    void signal();    //唤醒有等待此condition的所有线程    void signalAll();}

AQS的ConditionObject

ConditionObject的实现其实就是维护了两个队列

  1. condition队列:表示等待条件队列,其waitStatus=Node.CONDITION,由firstWaiterlastWaiter两个舒心控制。
  2. sync队列:表示可以竞争锁的队列,这个跟AQS的独占锁队列一致,与ReentrantLock独占锁共用一个队列。

await()方法分析

该方法就是把当前线程创建一个Node加入condition队列,释放当前占有的锁之后,接着就一直循环,判断当前节点在不在sync队列队列中,如果不在,则当前线程挂起;否则,就可以重新竞争独占锁。

public final void await() throws InterruptedException {    /*如果当前线程已经被中断*/    if (Thread.interrupted())        throw new InterruptedException();    Node node = addConditionWaiter();    /*释放当前线程持有的锁*/    int savedState = fullyRelease(node);    int interruptMode = 0;    /*通过循环,查看当前线程是否已经移入到sync队列中*/    while (!isOnSyncQueue(node)) {        /*如果没有在sync队列,那么将当前线程挂起*/        LockSupport.park(this);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;    }    /*当条件满足后,当前线程被唤醒    * 现在重新获取上一步中释放的锁    * */    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)        interruptMode = REINTERRUPT;    if (node.nextWaiter != null) // clean up if cancelled        unlinkCancelledWaiters();    if (interruptMode != 0)        reportInterruptAfterWait(interruptMode);}

添加一个等待Node到条件队列中

private Node addConditionWaiter() {    Node t = lastWaiter;    // If lastWaiter is cancelled, clean out.    /*如果最后一个等待节点的等待状态不是 CONDITION    * 说明已经被取消了,    * 那么则清理掉    * */    if (t != null && t.waitStatus != Node.CONDITION) {        unlinkCancelledWaiters();        t = lastWaiter;    }    /*新建一个Condition状态的节点,并将其加在尾部*/    Node node = new Node(Thread.currentThread(), Node.CONDITION);    if (t == null)        firstWaiter = node;    else        t.nextWaiter = node;    lastWaiter = node;    return node;}

释放当前的state,最终还是调用tryRelease方法

final int fullyRelease(Node node) {    boolean failed = true;    try {        /*获取当前的状态值,通过release方法释放锁*/        int savedState = getState();        if (release(savedState)) {            failed = false;            return savedState;        } else {            throw new IllegalMonitorStateException();        }    } finally {        if (failed)            node.waitStatus = Node.CANCELLED;    }}

判断是否在同步队列中

final boolean isOnSyncQueue(Node node) {    /*如果状态为CONDITION,说明还在condition队列中,还得循环等待*/    if (node.waitStatus == Node.CONDITION || node.prev == null)        return false;    /*当执行signal后,会执行enq方法,将node加入到队列中去*/    /*有前驱节点也有后置节点,那么一定在sync队列中*/    if (node.next != null)         return true;    /*     * 其前置节点为非null,但是也不在Sync也是可能的,     * 因为CAS将其加入队列失败.所以我们需要从尾部开始遍历确保其在队列     */    return findNodeFromTail(node);}

从尾部查找node节点

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

signal()方法分析

该方法就是把条件队列中头结点移动到sync队列中

public final void signal() {    /*如果不是独占锁模式,则抛出 非法监视器异常*/    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    //将condition队列的第一个节点出队列,并将其加入AQS的锁sync队列    Node first = firstWaiter;    if (first != null)        doSignal(first);}

释放信号

private void doSignal(Node first) {    do {        /*将firstWaiter指向下一个节点        * 如果下一个节点为空,则将lastWaiter置为空        * */        if ((firstWaiter = first.nextWaiter) == null)            lastWaiter = null;        /*断开头结点与其他节点的连接*/        first.nextWaiter = null;    } while (!transferForSignal(first) && (first = firstWaiter) != null);}

将一个节点从condition队列转换到Sync队列

final boolean transferForSignal(Node node) {    /*     * 所谓的condition队列和sync队列就在于waitStatus的值     * 在这里将waitStatus改为NORMAL,如果不能改变,说明当前节点已经被取消(CANCELLED),     * 那么重新尝试下一个节点     */    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))        return false;    /*     * 将当前节点移动到sync队列中,接着设置前驱节点等待状态,     * 如果被取消或者设置状态失败,那么唤醒该线程,让该线程重新同步,为了确保waitStatus是无害的     */    Node p = enq(node);/*当前节点的前驱节点*/    int ws = p.waitStatus;    /*如果前驱节点取消,或修改状态失败*/    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))        /*唤醒当前线程*/        LockSupport.unpark(node.thread);    return true;}

signalAll()方法分析

唤醒所有等待此condition的所有线程

public final void signalAll() {    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    Node first = firstWaiter;    if (first != null)        doSignalAll(first);}

遍历所有节点,使其加入到Sync队列

private void doSignalAll(Node first) {    lastWaiter = firstWaiter = null;    do {        Node next = first.nextWaiter;        first.nextWaiter = null;        transferForSignal(first);        first = next;    } while (first != null);}
原创粉丝点击