Condition 源码分析(JDK1.8)

1、Java的等待通知机制的发展

场景:我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停;等到线程B修改了条件condition,使condition满足了线程A的要求时,A再继续执行

1.1、自旋实现的等待通知

最简单的实现方法就是将condition设为一个volatile的变量,当A线程检测到条件不满足时就自旋

public class Test {
    private static volatile int condition = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!(condition == 1)) {
                    // 条件不满足,自旋
                }
                System.out.println("a executed");
            }
        });

        A.start();
        Thread.sleep(2000);
        condition = 1;
    }
}

这种方式的问题在于自旋非常耗费CPU资源,当然如果在自旋的代码块里加入Thread.sleep(time)将会减轻CPU资源的消耗,但是如果time设的太大,A线程就不能及时响应condition的变化,如果设的太小,依然会造成CPU的消耗。

1.2、Object提供的等待通知

java在Object类里提供了wait()和notify()方法,使用方法如下:

class Test1 {
    private static volatile int condition = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (!(condition == 1)) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    System.out.println("a executed by notify");
                }
            }
        });
        A.start();
        Thread.sleep(2000);
        condition = 1;
        synchronized (lock) {
            lock.notify();
        }
    }
}

 通过代码可以看出,在使用一个对象的wait()、notify()方法前必须要获取这个对象的锁。

当线程A调用了lock对象的wait()方法后,线程A将释放持有的lock对象的锁,然后将自己挂起,直到有其他线程调用notify()/notifyAll()方法或被中断。可以看到在lock.wait()前面检测condition条件的时候使用了一个while循环而不是if,那是因为当有其他线程把condition修改为满足A线程的要求并调用notify()后,A线程会重新等待获取锁,获取到锁后才从lock.wait()方法返回,而在A线程等待锁的过程中,condition是有可能再次变化的。

因为wait()、notify()是和synchronized配合使用的,因此如果使用了显示锁Lock,就不能用了。所以显示锁要提供自己的等待/通知机制,Condition应运而生。

1.3、显示锁提供的等待通知

Condition实现上面的例子:

class Test2 {
    private static volatile int condition = 0;
    private static Lock lock = new ReentrantLock();
    private static Condition lockCondition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    while (!(condition == 1)) {
                        lockCondition.await();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
                System.out.println("a executed by condition");
            }
        });
        A.start();
        Thread.sleep(2000);
        condition = 1;
        lock.lock();
        try {
            lockCondition.signal();
        } finally {
            lock.unlock();
        }
    }
}

可以看到通过 lock.newCondition() 可以获得到 lock 对应的一个Condition对象lockCondition ,lockCondition的await()、signal()方法分别对应之前的Object的wait()和notify()方法。整体上和Object的等待通知是类似的。

1.4、Condition 相比较于 Object.wait() 的优势

上面我们看到了Condition实现的等待通知和Object的等待通知是非常类似的,而Condition提供的等待通知功能更强大,最重要的一点是,一个lock对象可以通过多次调用 lock.newCondition() 获取多个Condition对象,也就是说,在一个lock对象上,可以有多个等待队列,而Object的等待通知在一个Object上,只能有一个等待队列。

 

2、Condition 接口介绍

2.1、简介

condition与Lock的实现类结合使用。 如果Lock替换了synchronized方法和语句的使用,则Condition将替换Object监视方法(wait,notify和notifyAll)的使用。

condition,也称为condition queue或者condition variables,能让一个线程阻塞在条件变量上,直到其他线程通知该线程条件变量现在可能为true。当一个线程等待条件时,它会自动释放相关联的锁,并陷入阻塞状态,就跟Object.wait()方法一样。

一个Condition实例一个Lock实例绑定,要获取特定Lock实例的Condition实例,可以调用Lock实例的newCondition()方法。

2.2、特点

  1. Condition内部主要是由一个装载线程节点 Node 的 Condition Queue 实现
  2. 对 Condition 的方法(await, signal等) 的调用必需是在本线程获取了独占锁的前提下
  3. 因为 操作Condition的方法的前提是获取独占锁, 所以 Condition Queue 内部是一条不支持并发安全的单向 queue (这是相对于 AQS 里面的 Sync Queue)

2.3、应用举例

以生产者/消费者模式为例,假设我们有一个有界缓冲区,它支持put和take方法。 如果缓冲区为空,则消费线程将阻塞,直到缓冲区有内容为止; 如果缓冲区已满,则生产线程阻塞,直到缓冲区有空间可用为止。 我们希望把put线程和take线程放在不同的waitset中,以便我们在缓冲区中的内容可用或空间可用时,实现只通知一个线程。 这可以使用两个Condition实例来实现。

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); 
    final Condition notEmpty = lock.newCondition(); 
 
    final Object[] items = new Object[100];
    int putptr, takeptr, count;
 
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
             while (count == items.length)
             notFull.await();
             items[putptr] = x;
             if (++putptr == items.length) putptr = 0;
             ++count;
             notEmpty.signal();
        } finally {
             lock.unlock();
        }
    }
 
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
            notEmpty.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}
  •  首先需要获得锁,是为了确保数组修改的可见性和排他性
  • 用while循环而非if判断,是为了防止过早或者意外的通知,只有符合条件才能够退出循环


3、Condition 接口源码分析

public interface Condition {

    void await() throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

只是定义了一些和 await() 、signal() 相关的方法,没有具体实现。

 

4、AQS 中的 Condition 实现 ConditionObject

ConditionObject 是AQS 中的内部类,其实现了 Condition 和 Serializable 接口。

4.1、ConditionObject 的成员变量 和 构造方法

private static final long serialVersionUID = 1173984872572414699L;  // 版本序列号

// Node 节点具体结构看AQS的Node内部类
// Condition queue(等待队列|条件队列)头节点
private transient Node firstWaiter;
// 等待队列尾节点
private transient Node lastWaiter;

// 从等待退出时重新中断
private static final int REINTERRUPT =  1;
// 从等待退出时抛出异常
private static final int THROW_IE    = -1;

// 构造方法
public ConditionObject() { }

等待队列(Condition queue)是一个FIFO队列,队列中每个节点包含一个线程引用,该线程就是在Condition对象上等待的线程。

 

4.2、await() 调用流程分析

当前线程执行await()方法之前需要先获得 lock 的锁,执行完await()之后,线程释放锁,进入等待状态,直到被通知(signal)或中断,当前线程才能从await()方法返回(注意:不是立刻返回,返回之前,线程必须重新获得lock的锁,并且和CPU时间片分配有关)

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();   // 响应中断
    // 1.当前线程进入等待队列尾部(且顺便清除等待队列中cancelled的节点)
    Node node = addConditionWaiter();  

    // 2.释放同步状态(释放锁lock,不管获取几次,都释放为0),同时唤醒第一个有效后继节点
    int savedState = fullyRelease(node);  
    int interruptMode = 0;  //初始化 interruptMode

    // 3.如果当前节点没有在同步队列上,即还没有被signal,则将当前线程阻塞(这就达到了wait的效果)
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        /**后面的蓝色代码都是和中断相关的,主要是区分两种中断:是在被signal前中断还是在被signal后中断,
         *如果是被signal前就被中断则抛出 InterruptedException,否则执行 Thread.currentThread().interrupt();
         */
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)   // 0表示没有被中断
            break;  // 被中断过|唤醒 过,则退出while循环
    }
    
    //注意:退出了上面自旋说明当前节点已经在同步队列上,但是当前节点不一定在同步队列队首。
    // 4.acquireQueued将阻塞直到当前节点成为队首,即当前线程获得了锁。
    //然后await()方法就可以退出了,让线程继续执行await()后的代码。
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  //acquireQueued具体实现在AQS中,循环tryAcquire()
        interruptMode = REINTERRUPT;

    // 5.清除等待队列中的cancelled节点
    if (node.nextWaiter != null) 
        unlinkCancelledWaiters();  

    // 6.根据interuptMode值,来决定是 抛出中断 | 当前线程自我中断 | 什么都不做(就是看中断是不是由signal引起的)
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

第1步: 当前线程进入等待队列尾部(且顺便清除等待队列中cancelled的节点)

addConditionWaiter()

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 1.如果尾节点被cancelled,则清除等待队列中的cancelled节点
    if (t != null && t.waitStatus != Node.CONDITION) {  
        unlinkCancelledWaiters();  // 清除等待队列中cancelled的节点
        t = lastWaiter;
    }
    // 2.将当前线程加入等待队列尾部
    Node node = new Node(Thread.currentThread(), Node.CONDITION); // 将当前线程创建成一个Node节点,且waitStatus=CONDITION
    if (t == null)  // 等待队列空
        firstWaiter = node;  // 则新建节点作为头节点加入等待队列
    else
        t.nextWaiter = node;  // 将当前节点放入等待队列尾部
    lastWaiter = node;  // 更新尾节点
    return node;
}

unlinkCancelledWaiters()

// 从头到尾扫描等待队列,删除等待队列中,节点waitStatus!=CONDITION的节点
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;  // t是工作节点(从头扫描到尾)
    Node trail = null;   // 记录当前最后的合格节点(CONDITION表示在等待队列上)
    while (t != null) {
        Node next = t.nextWaiter;  // next 是工作节点t的下一个节点
        if (t.waitStatus != Node.CONDITION) {  // 当前节点状态不是在CONDITION上
            t.nextWaiter = null;  // 将 t 的后继节点指针置空
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;  // 在等待队列中删除t节点(GC)
            if (next == null)  // 到达等待队列尾部
                lastWaiter = trail;
        }
        else
            trail = t;  // 更新满足waitStatus=CONDITION的最后一个节点
        t = next;  //更新工作节点
    }
}

第2步:释放同步状态(释放锁lock,不管获取几次,都释放为0),同时唤醒第一个有效后继节点

 fullyRelease(Node node)

// 完全释放节点node的同步状态
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();  // 获取同步状态值
        if (release(savedState)) { // 尝试释放同步状态(saveStatus表示全部释放),并且唤醒下一个有效节点
            failed = false;  // 设置释放成功
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED; // 如果完全释放失败,设置节点状态为cancelled
    }
}

release(int arg)

// 释放同步锁,并且唤醒下一个有效的后继节点
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  // 唤醒头节点h后面的第一个有效节点
        return true;
    }
    return false;
}

unparkSuccessor(Node node)

// 唤醒节点node后面的第一个有效节点(非空,非cancelled)
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {  // head的next节点为null或者cancelled
        s = null;  // cancelled时,s置空
        for (Node t = tail; t != null && t != node; t = t.prev)  // 从tail往前找直到s或null
            if (t.waitStatus <= 0)
                s = t;
        // 经过上面的循环,s最后的取值是最前的一个waitStatus<=0的节点(可以被唤醒的节点)
    }
    if (s != null)
        LockSupport.unpark(s.thread);  // 唤醒节点 s 的线程
}

第3步:如果当前节点没有在同步队列上,即还没有被signal,则将当前线程阻塞(这就达到了wait的效果)

checkInterruptWhileWaiting(Node node)

//检查是否中断,如果在发出信号之前中断,则返回THROW-IE,如果在发出信号之后REINTERUPT,如果没有中断,则返回0
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}

transferAfterCancelledWait(Node node)

// 如有必要,在取消等待后将节点传输到同步队列。如果线程在发出信号之前被取消,则返回true
final boolean transferAfterCancelledWait(Node node) {
    // 1.如果修改节点状态(CONDITION --> 0)成功,则节点入 同步队列尾部,返回true
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {  // unsafe.compareAndSwapInt
        enq(node);  // 循环CAS将node放到 同步队列 尾部
        return true;
    }
    
    // 设置节点状态失败,表明节点不是CONDITION,说明节点已经被唤醒了(signal),因此返回false
    // 2.如果节点不在同步队列上,就挂起当前线程
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

第6步:根据interuptMode值做收尾工作

reportInterruptAfterWait(int interruptMode)

// 根据interruptMode的值来做事:抛出异常 | 当前线程自我中断  |  什么都不做
// 在signal发出之前中断 : 抛出中断异常
// 在signal发出之后中断 : 当前线程自我中断
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

 

4.3、awaitUninterruptibly()    对中断不敏感

和await()的区别:

  1. 前面没有判断中断
  2. 线程被中断,设置为true,而不是抛出异常
  3. 不管是signal之前 还是 之后的中断,最后的操作都是 selfInterrupt()

对中断不敏感,不是完全不关心中断。而是不再区分 signal之前 和 signal之后的中断

public final void awaitUninterruptibly() {
    // 区别1:没有了对中断的判断
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    boolean interrupted = false;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        // 区别2:当前线程被中断,设置true,而不是像以前抛出异常
        if (Thread.interrupted())   
            interrupted = true;
    }
    if (acquireQueued(node, savedState) || interrupted)
        selfInterrupt();   // 只要是中断就 seltInterrupt()
}

4.4、awaitNanos(long nanosTimeout)     超时(时间段)

基本流程和await()一样,区别是:

  1. 检查剩余时间<=0,则超时,立即将node移到同步队列
  2. 剩余时间>=CAS自旋微时,则将当前线程park
  3. 更新剩余时间
  4. 上面都是在while循环中实现的
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    // 区别1.加入了截至时间deadline
    final long deadline = System.nanoTime() + nanosTimeout;  
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // 区别2:剩余时间<=0,表示超时
        if (nanosTimeout <= 0L) {
            transferAfterCancelledWait(node);  //将node移到同步队列上
            break;
        }
        // 区别3:剩余时间 >= CAS自旋微时,则将当前线程park
        if (nanosTimeout >= spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanosTimeout);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        // 区别4:更新剩余时间
        nanosTimeout = deadline - System.nanoTime();  
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    // 区别5:返回剩余时间
    return deadline - System.nanoTime();
}

4.5、awaitUntil(Date deadline)      超时(截至时间)

和await()的区别:

  1. 当前时间和截至时间比较,若超时,则将node移到同步队列,退出while循环
  2. 如果没有超时,则将当前线程 park
public final boolean awaitUntil(Date deadline) throws InterruptedException {
    // 区别1:取得Date的时间戳
    long abstime = deadline.getTime();
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    // 区别2:超时标志
    boolean timedout = false;
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // 区别3:当前时间是否超过截至时间,超过,则将node节点移到同步队列,退出while循环
        if (System.currentTimeMillis() > abstime) {
            timedout = transferAfterCancelledWait(node);
            break;
        }
        // 区别4:如果没有超时,则将当前线程park
        LockSupport.parkUntil(this, abstime);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    return !timedout;
}

4.6、await(long time, TimeUnit unit)

public final boolean await(long time, TimeUnit unit) throws InterruptedException {
    // 区别1:获取unit的毫秒数
    long nanosTimeout = unit.toNanos(time);
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    // 区别2:计算截至时间
    final long deadline = System.nanoTime() + nanosTimeout;
    boolean timedout = false;
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        // 区别3:超时,则将node移到同步队列,退出while循环
        if (nanosTimeout <= 0L) {
            timedout = transferAfterCancelledWait(node);
            break;
        }
        // 区别4:剩余时间>=CAS自旋微时,park当前线程
        if (nanosTimeout >= spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanosTimeout);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        nanosTimeout = deadline - System.nanoTime();  // 更新剩余时间
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    return !timedout;
}

4.7、signal()     唤醒Condition队列中的第一个非CANCELLED节点线程

// 前提是线程已经获取lock
public final void signal() {
    // 1.当前线程没有获取锁,则抛出异常
    if (!isHeldExclusively())   
        throw new IllegalMonitorStateException();
    // 2.唤醒等待队列头节点
    Node first = firstWaiter;
    if (first != null)    // 等待队列头节点不空,唤醒first节点
        doSignal(first);
}

doSignal(Node first)

// 唤醒first节点(就是first节点入同步队列,并尝试SIGNAL)
private void doSignal(Node first) {
    do {
        // 1.等待队列,头节点后继节点为空,则尾节点置空(新的等待队列为空)
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;  // GC原来的firstWaiter,因为它已经出等待队列了
    } while (!transferForSignal(first) &&   // 尝试将first节点移到同步队列,并设置SIGNAL
             (first = firstWaiter) != null);  // 若上步失败,first后移一个节点
}

transferForSignal(Node node)

// 将first节点移到 同步队列,并尝试设置它为SIGNAL
final boolean transferForSignal(Node node) {  
    // 1.尝试设置first节点状态(CONDITION --> 0),失败(当前节点已被中断)则返回false   
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 
        return false;

    // 2.将节点first入 同步队列  
    Node p = enq(node);   // 返回node在同步队列中的前驱节点
    int ws = p.waitStatus;

    // 3.如果新节点 cancelled或者前驱节点设置signal失败,则应该 park 它的线程
    //如果先驱节点的状态为CANCELLED(>0) 或设置先驱节点的状态为SIGNAL失败,那么就立即唤醒当前节点对应的线程,
    //线程被唤醒后会执行acquireQueued方法,该方法会重新尝试将节点的先驱状态设为SIGNAL并再次park线程;
    //如果当前设置前驱节点状态为SIGNAL成功,那么就不需要马上唤醒线程了,当它的前驱节点成为同步队列的首节点且释放同步状态后,会自动唤醒它。
    //其实笔者认为这里不加这个判断条件应该也是可以的。只是对于CAS修改前驱节点状态为SIGNAL成功这种情况来说,
    //如果不加这个判断条件,提前唤醒了线程,等进入acquireQueued方法了节点发现自己的前驱不是首节点,还要再阻塞,等到其前驱节点成为首节点并释放锁时再唤醒一次;
    //而如果加了这个条件,线程被唤醒的时候它的前驱节点肯定是首节点了,线程就有机会直接获取同步状态从而避免二次阻塞,节省了硬件资源
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

唤醒流程:

  1. 当前线程不占有锁,则抛出异常
  2. 等待队列头节点非空,则唤醒头节点
  3. 尝试将等待队列头节点 移到 同步队列尾部,并尝试设置为SIGNAL(设置失败,或者cancelled,park当前线程),如果失败,则在等待队列中往后遍历

4.8、signalAll()     而signalAll就是唤醒所有非CANCELLED节点线程,当然了遇到CANCELLED线程就需要将其从FIFO队列中剔除。

public final void signalAll() {
    // 1.当前线程没有获取锁,则抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    // 2.唤醒全部节点
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first); 
}

doSignalAll(Node first)

// 释放等待队列全部节点
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;   // 方便GC 原来的头节点
        transferForSignal(first);  // 将first节点移到同步队列,并尝试设置SIGNAL
        first = next;   // 更新工作节点
    } while (first != null);   // 一直到等待队列尾部
}

基本上和 signal() 方法一样,就是在唤醒一个节点之后,依次唤醒后面的节点,直到等待队列的节点全部唤醒完

 

5、同步队列 和 等待队列的比较

(1)两种队列中的节点是一个AbstractQueuedSynchronizer.Node实例,每个等待队列中的firstWaiter 和 lastWaiter(这个不是Node节点的属性,而是等待队列的属性);同步队列中也有 head 和 tail 节点(不是Node属性,是同步队列的属性)

(2)Node节点介绍

Node的主要字段有:

  1. waitStatus:等待状态,所有的状态见下面的表格。
  2. prev:前驱节点
  3. next:后继节点
  4. thread:当前节点代表的线程
  5. nextWaiter:Node既可以作为同步队列节点使用,也可以作为Condition的等待队列节点使用(将会在后面讲Condition时讲到)。在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;在作为等待队列节点使用时,nextWaiter保存后继节点。
状态含义
CANCELLED1当前节点因为超时或中断被取消同步状态获取,该节点进入该状态不会再变化
SIGNAL-1标识后继的节点处于阻塞状态,当前节点在释放同步状态或被取消时,需要通知后继节点继续运行。每个节点在阻塞前,需要标记其前驱节点的状态为SIGNAL。
CONDITION-2标识当前节点是作为等待队列节点使用的。
PROPAGATE-3 
00初始状态

(3)AQS的同步排队用了一个隐式的双向队列;Condition实现等待的时候内部也有一个等待队列,等待队列是一个隐式的单向队列

(4)一个AQS中:同步队列只有一个,而等待队列数量n>=0(一个ConditionObject对应一个)

(5)这里为什么要设计 双向队列 和 单项队列 

之所以同步队列要设计成双向的,是因为在同步队列中,节点唤醒是接力式的,由每一个节点唤醒它的下一个节点,如果是由next指针获取下一个节点,是有可能获取失败的,因为虚拟队列每添加一个节点,是先用CAS把tail设置为新节点,然后才修改原tail的next指针到新节点的。因此用next向后遍历是不安全的,但是如果在设置新节点为tail前,为新节点设置prev,则可以保证从tail往前遍历是安全的。因此要安全的获取一个节点Node的下一个节点,先要看next是不是null,如果是null,还要从tail往前遍历看看能不能遍历到Node。

而等待队列就简单多了,等待的线程就是等待者,只负责等待,唤醒的线程就是唤醒者,只负责唤醒,因此每次要执行唤醒操作的时候,直接唤醒等待队列的首节点就行了。等待队列的实现中不需要遍历队列,因此也不需要prev指针。

 

6、AQS中的节点流动示例(同步队列 和 等待队列之间)

Condition拥有首节点的引用,而新增节点只需要将原尾节点的nextWaiter指向它,并更新尾节点即可。需要注意的是节点更新的过程是没有使用CAS方法的,原因是调用await 方法的线程必定获取了锁。

 

 

I.初始化状态:AQS等待队列有3个Node,Condition队列有1个Node(也有可能1个都没有)

 

II.节点1执行Condition.await()

1.将head后移
2.释放节点1的锁并从AQS等待队列中移除
3.将节点1加入到Condition的等待队列中
4.更新lastWaiter为节点1

 

III.节点2执行signal()操作
5.将firstWaiter后移
6.将节点4移出Condition队列
7.将节点4加入到AQS的等待队列中去
8.更新AQS的等待队列的tail

 

 

7、参考文章列表

(1)java Condition源码分析     https://blog.csdn.net/coslay/article/details/45217069

(2)Java显示锁学习总结之六:Condution 源码分析   https://www.cnblogs.com/sheeva/p/6484224.html

(3)【Java并发编程】Condition源码解析     https://www.jianshu.com/p/a92e0dcd15e8

(4)Condition 源码分析 (基于Java 8)     https://www.jianshu.com/p/52089c4eefdd

(5)Condition源码分析     https://blog.csdn.net/qq_26222859/article/details/81131865

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值