从Condition到AQS

写在前面

Condition源码学习AQS

demo

Condition必须依赖lock,需要在锁下创建condition。类似wait、notify或notifyAll 与 synchronized 的关系

咱们根据这个demo来解读condition代码

public static void main(String[] args) {
    
    
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    new Thread(()->{
    
    
       lock.lock();
        System.out.println("线程1抢占到了这把锁!");
        try {
    
    
            Thread.sleep(4000);
            condition.await();
            System.out.println("线程1继续执行!");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        lock.unlock();
    },"线程1").start();
    try {
    
    
        Thread.sleep(500);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    new Thread(()->{
    
    
        lock.lock();
        System.out.println("线程2抢占到了这把锁!");
        try {
    
    
            condition.signalAll();
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("我唤醒了其他线程!");
        lock.unlock();
    },"线程2").start();
    try {
    
    
        Thread.sleep(500);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    new Thread(()->{
    
    
        lock.lock();
        System.out.println("线程3抢占到了这把锁!");
        try {
    
    
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        lock.unlock();
    },"线程3").start();
}

执行结果

线程1抢占到了这把锁!
线程2抢占到了这把锁!
我唤醒了其他线程!
线程3抢占到了这把锁!
线程1继续执行!
lock.newCondition()
//默认创建ConditionObject
final ConditionObject newCondition() {
    
    
    return new ConditionObject();
}
condition.await();

public final void await() throws InterruptedException {
    
    
    //判断线程是否中断过
    if (Thread.interrupted())
        throw new InterruptedException();
    //将当前线程加入到条件队列
    Node node = addConditionWaiter();
    //释放clh队列的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //是否在条件队列中
    while (!isOnSyncQueue(node)) {
    
    
        //阻塞当前线程
        //什么是否被唤醒呢,此时已经释放锁
        //当执行unlock时会将队列中的线程按照队列顺序执行
        //当当前节点的前置节点执行unlock时会唤醒这个中断的线程
        LockSupport.park(this);
        //这里被唤醒可能是正常的signal操作也可能是中断
        //如果线程中断过直接抛出异常
        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);
}

将当前节点加入到条件队列

private Node addConditionWaiter() {
    
    
     Node t = lastWaiter;
     if (t != null && t.waitStatus != Node.CONDITION) {
    
    
         //取消链表中不是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;
 }
  • 条件队列与clh队列不同,clh队列是一个双向链表,此处是一个单向链表
  • clh队列有一个默认的head节点,条件队列是没有的,每个节点都是业务节点
  • 执行此段代码只会有一个线程执行,因为执行此段代码前已经加完锁了,此处执行就是获取到锁的线程

fullyRelease 方法
唤醒条件队列

final int fullyRelease(Node node) {
    
    
    boolean failed = true;
    try {
    
    
        //获取加锁状态
        int savedState = getState();
        //释放锁,前一节我们已经分析过代码
        //就是将head的下一个节点进行唤醒
        if (release(savedState)) {
    
    
            failed = false;
            return savedState;
        } else {
    
    
            throw new IllegalMonitorStateException();
        }
    } finally {
    
    
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

isOnSyncQueue

  • 用于判断是否在阻塞队列中(就是clh队列)
  • 当前一步唤醒阻塞队列时,有可能有的线程已经执行了singleAll方法将条件队列的此节点加入到阻塞队列中
  • 此方法就是判断如果没有将条件队列的此节点加入到阻塞队列时,此时需要阻塞住当前线程
  • 如果加入到阻塞队列,尝试获取锁,并在阻塞队列中进行排队等待

condition.signalAll();

/**
 * 唤醒所有条件队列的节点转移到同步队列当中
 */
public final void signalAll() {
    
    
    //用于判断当前线程是否是获取到锁的线程,只有获取到锁的线程执行
    //await 释放锁  signal 不会示释放锁 同 wait 与 notify
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //获取到条件的头 就是第一个节点
    Node first = firstWaiter;
    if (first != null)
        //执行
        doSignalAll(first);
}
private void doSignalAll(Node first) {
    
    
     //将条件队列链表置为空
     lastWaiter = firstWaiter = null;
     do {
    
    
         //先获取下第二个node节点
         Node next = first.nextWaiter;
         //将第一个节点抽取出来
         first.nextWaiter = null;
         //加入到条件队列
         transferForSignal(first);
         //第二个节点赋值到第一个节点
         first = next;
     } while (first != null);
 }
  • 代码中部分说明已经注释
  • 循环执行将条件队列中所有等待的线程全部都加入到阻塞队列中
  • 在阻塞队列中根据重入锁逻辑等待获取锁

transferForSignal

final boolean transferForSignal(Node node) {
    
    
        /*
         * 修改节点信号量状态为0,失败直接返回false
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        /*
         * 加入同步队列尾部当中,返回前驱节点
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        //前驱节点不可用 或者 修改信号量状态失败
        //如果前驱节点不等于-1说明不会唤醒后面节点 这不是我们想要的 此时我们唤醒他
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread); //唤醒当前节点
        return true;
    }

加入到条件队列后,就跟阻塞队列其余线程一样,等待前驱节点的唤醒

我们demo的大致流程为

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37904966/article/details/113446352