Condition 源码学习

4 篇文章 0 订阅

是什么

任意一个Java对象,都拥有一组监视器方法(Object类上)

  1. wait()
  2. wait(long timeout)
  3. notify()
  4. notifyAll()
    这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。

使用例子

获取一个Condition必须通过Lock的newCondition()方法。只有AQS有Condition的实现类,Lock有获取Condition的方法。并且Condition需要与Lock联合使用
当A线程调用await()方法后,A线程会释放锁并在此等待。
其他线程调用Condition对象的signal()方法,通知A线程后才从await()方法返回,并且在返回前已经获取了锁。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        condition.await();
        dosomething();
    } finally {
        lock.unlock();
    }
}

public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

接口结构

在这里插入图片描述

方法名作用
void await()使当前线程等待,直到收到信号或被中断interrupted,关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态直到:1.其他线程调用signal方法,并且当前线程恰好被选择为要唤醒的线程2.其他一些线程调用signalAll;3.某些其他线程interrupts当前线程;4.发生虚假唤醒。在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁
void awaitUninterruptibly();相比await(),线程不可被中断
long awaitNanos(long nanosTimeout)使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间, 相对await()多了超时唤醒,返回值为剩余时间
boolean await(long time, TimeUnit unit)使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间
boolean awaitUntil(Date deadline)当前线程进入等待状态,直到被通知或中断,或者经过指定的时间,被通知返回true,到指定时间返回false
void signal();唤醒一个等待线程。 该线程必须重新获取锁,然后才能从await返回。
void signalAll();唤醒所有等待的线程。 每个线程必须重新获取锁,然后才能从await返回。

两个名词

  1. 同步队列

存放在竞争锁时暂时没获取到锁的线程的队列,等待前面的节点释放锁后唤醒

  1. 等待队列

存放 调用Condition.await方法,线程进入等待状态,需要等其他线程调用Condition.signal的线程

实现类ConditionObject

属性

Condition拥有首节点(firstWaiter)和尾节点(lastWaiter),有头节点就能构建一个等待队列FIFO。

  • private transient Node firstWaiter;

等待队列的第一个节点。

  • private transient Node lastWaiter;

等待队列的最后一个节点。

在这里插入图片描述

void await()

实现可中断条件等待

  1. 构建节点,加入到等待队列尾部
  2. 释放锁,唤醒同步队列中的后继节点
  3. 循环判断,节点是否已经在同步队列,不在的话线程挂起
  4. 等待线程被唤醒,再判断线程是否在同步队列
  5. 在的同步队列的话,参与竞争锁
public final void await() throws InterruptedException {
    //线程被中断抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //构建节点,插入到等待队列尾部
    Node node = addConditionWaiter();
    //释放锁
    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) 
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

几个注意点:

  1. 为什么没看到CAS操作:因为发生的一切都是在获取到锁的情况下发生的,依靠锁实现同步状态
  2. 线程是在什么时候被移动在同步队列的:当其他线程调用signal()方法,并且等待队列中被选中的是该线程

void signal()

  1. 先将等待队列中的头节点移出,修改相应引用
  2. 将节点移动到同步队列中
  3. 判断同步队列的原尾节点状态为Cancel,或者修改状态为Signal失败时,唤醒该线程投入锁的竞争。
  4. 否则等待同步队列前驱节点释放锁后唤醒线程。
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);
}
//将节点从条件队列转移到同步队列。如果成功,则返回true
final boolean transferForSignal(Node node) {
    //将该节点从状态CONDITION改变为初始状态0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    //将节点加入到同步队列中去,返回同步队列原尾节点
    Node p = enq(node);
    int ws = p.waitStatus;
    //检查前驱节点的状态,若状态为cancel,尝试唤醒节点
    //前驱节点不为取消转态,尝试CAS状态为SIGNAL,如果失败,尝试唤醒节点投入到锁的竞争中去
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

线程在调用signal后就会将等待队列的头节点移动到同步队列。等待同步队列的前驱节点唤醒线程。
在这里插入图片描述

signalAll就算将所有节点都移动到同步队列中。

总结

  1. 一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到等待队列中,然后释放锁。
  2. 将线程挂起,等待其他线程调用signal()将线程移动到同步队列中
  3. 当线程被唤醒后,尝试竞争锁,竞争到锁后会从await方法进行返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值