Condition

在第二章第5节已经说了Conditon, 当时用Condition做了一个有界队列。

Condition是Lock的附属物,Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。

一个锁可以搞多个Condition

Condition的API

public interface Condition {
    /**
     * 让当前线程进入等待状态直到被通知或者被中断,当前线程重新进入运行状态且从await方法返回的情况如下
     * 1. 其他线程调用了该Condition的signal 或signalAll
     * 2. 其他线程调用interrupt中断了当前线程
     * @throws InterruptedException
     */
    void await() throws InterruptedException;
    /**
     * 等价上面那个
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    /**
     * 指定到某个时间还没被通知且没被中断直接返回,提前被通知了返回true,否则false
     * @param deadline
     * @return
     * @throws InterruptedException
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;
    /**
     * 唤醒等待在Condition上的一个线程
     */
    void signal();
    /**
     * 唤醒所有等待在Condition上的线程
     */
    void signalAll();
}

基本原理

在ReentrantLock里边搜newCondition

// 我们拿Condition都是通过Lock的newCondition接口拿的
// 但是我们发现他是掉的AQS的newCondition方法
public Condition newCondition() {
    return sync.newCondition();
}

// 在找到AQS的newCondition,发现这个是自己写的
final ConditionObject newCondition() {
    return new ConditionObject();
}

可见ConditionObject应该是实现了Condition接口,去看一眼

发现ConditionObject是AQS的内部类

等待队列

去看ConditionObject, 会发现里边也和AQS同步队列类似,有一个队列结构,队列节点居然和之前说的同步队列的节点是一样的,世间真奇妙

ConditionObject里边有两个重要成员变量firstWaiter,lastWaiter,对比AQS,AQS有head和tail,这个是双向队列的模板,太巧了

当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 构建了一个Node放到等待队列的队尾
    Node node = addConditionWaiter();
    // 我没仔细看,这里大概的意思是会把这个节点从同步队列移除,并且释放锁
    //   因为调用await是获取锁的
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        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);
}

简单理解,await就是根据当前线程信息构建Node节点,加入等待队列,同时由于调用await的一定是获取锁的,所以我们要移除当前线程在同步队列的那个节点,其实就是首节点,首节点要变,首节点的线程就要释放锁,其实就是当前线程会释放锁。

上边这段描述是我看简单搂了一眼源码写的,发现竟然和书上意思一样,开心~

接下来就是看signal了,感觉能猜到他是怎么做的了

public final void signal() {
    // 必须要获取锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
                (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    // enq前面分析过,就是同步队列的入队
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 很关键,唤醒
        LockSupport.unpark(node.thread);
    return true;
}
``

做了几个事,等待队列首节点出队,将这个首节点入队到同步队列,唤醒这个首节点的线程,唤醒之后就会去竞争锁(或者叫同步状态)了

await方法里面的while (!isOnSyncQueue(node)), 将会不满足条件了,所以await方法会返回

可以发现调用了signal 不是await的地方会立即执行的,需要先抢到锁才会继续执行

signalAll就是把等待队列中的所有节点都转移到同步队列,并唤醒

```java
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

results matching ""

    No results matching ""