Condition(Java)

m0_55321413 2023-11-17 22:19:17

Condition

Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒

Condition 的基本使用

调用了 await() 后,当前线程会释放锁并且等待,其他线程 signal() / signalall() 通知阻塞的线程并且释放锁,被唤醒的线程获得锁并且继续执行,最后释放锁。

public class ConditionWait implements Runnable {
    private Lock lock;
    private Condition condition;

    public ConditionWait(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            System.out.println("begin - ConditionDemoWait");
            lock.lock();// 获得锁
            condition.await();// 释放锁并等待,此时会将当前线程封装为Node放入到 condition 队列中
            System.out.println("end - ConditionDemoWait");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ConditionSignal implements Runnable {
    private Lock lock;
    private Condition condition;

    public ConditionSignal(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try{
            System.out.println("begin - ConditionDemoSignal");
            lock.lock();
            condition.signal();// 唤醒一个阻塞线程,从 condition 队列中将 Node 转换到 AQS 队列中
            System.out.println("end - ConditionDemoSignal");
        }finally {
            lock.unlock();
        }
    }
}

public class ConditionMain {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock(); // 重入锁
        Condition condition=lock.newCondition();
        new Thread(new ConditionWait(lock,condition)).start();
        new Thread(new ConditionSignal(lock,condition)).start();
    }
}

condition 源码

调用 Condition,需要获得 Lock 锁,所以也一定会存在有一个 AQS 队列

1、condition.await

使当前的线程进入等待队列并且释放锁,同时当前线程状态变为等待状态,从 await() 返回时,一定获取了 Condition 相关的锁

public final void await() throws InterruptedException {
  if (Thread.interrupted()) throw new InterruptedException(); // 处理中断
      Node node = addConditionWaiter(); // 创建一个节点,状态为 condition,结构依旧是链表
      int savedState = fullyRelease(node); // 释放当前的锁,并且保留重入次数,唤醒 AQS 队列线程
      int interruptMode = 0;
      // 判断节点是否是在 AQS 队列中,第一次判断是 false,因为已经释放锁
      while (!isOnSyncQueue(node)) {
         LockSupport.park(this); // 挂起当前线程
         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)           
            break;
         }
      // 线程被唤醒,尝试获取锁,acquireQueued 返回 false 就是获取到锁
      // interruptMode != THROW_IE 表示这个线程没有成功的将 Node 入队,但是 signal 执行了 enq
      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
         interruptMode = REINTERRUPT;
      if (node.nextWaiter != null) // clean up if cancelled
         // 如果 node 的下一个等待者不是 null,则清理 condition 队列的节点 
         unlinkCancelledWaiters();
      if (interruptMode != 0) // 线程被中断
         reportInterruptAfterWait(interruptMode);
}

2、addConditionWaiter

这个方法,用于将当前线程封装为 Node 并且添加到等待队列,这里的队列是单向链表

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 如果 lastWaiter != null,并且 waitStatus != CONDITION,将节点移除
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 构建一个 Node,waitStatus= CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
       firstWaiter = node;
    else
       t.nextWaiter = node;
       lastWaiter = node;
       return node;
}

3、fullyRelease

彻底的释放锁,如果是重入锁,只需要释放一次,就会把所有的重入次数清空

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        // 获取重入次数
        int savedState = getState();
        if (release(savedState)) { // 释放锁并且唤醒下一个同步队列中的等待线程
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

4、isOnSyncQueue

在 condition 中,被唤醒的节点会被转移到 AQS 同步队列
判断当前节点是否在同步队列中,存在返回true
如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同步锁,所以需要把当前线程阻塞起来,直到其他的线程调用 signal 唤醒
如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得执行程序执行权限

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) 
        return true;
    return findNodeFromTail(node);
}

5、Condition.signal

public final void signal() {
    // 判断当前线程是否获得了锁
    if (!isHeldExclusively()) throw new IllegalMonitorStateException();
    Node first = firstWaiter; // 获取 condition 队列中的第一个 Node
    if (first != null) doSignal(first);
}

6、Condition.doSignal

对 condition 中的第一个节点,进行 transferForSignal 操作,将 node 从 condition 中转移到 AQS 队列,同时修改 AQS 尾部节点状态

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

7、AQS.transferForSignal

先是 CAS 修改了节点状态,如果成功,就将这个节 点放到 AQS 队列中,然后唤醒这个节点上的线程

final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node); // 调用 enq 将 node 加入到 AQS,返回加入后节点的上一个节点,也就是 tail
    int ws = p.waitStatus;
    // 如果上一个节点的状态被取消,或者尝试设置上一个节点的状态为 SIGNAL 失败
    // SIGNAL 表示 next 节点需要被阻塞
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 唤醒线程
    // 如果 node 的 pre 节点已经是 SIGNAL 状态,那么阻塞的线程需要由 AQS 队列进行唤醒
    return true; 
}

Condition 总结

阻塞:await()方法中,在线程释放锁资源之后,如果节点 不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal()后,节点会从 condition 队列移动到 AQS 等待队列,则进入正常锁的获取流程

...全文
42 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

490

社区成员

发帖
与我相关
我的任务
社区描述
闽江学院IT领域专业的学生社区
社区管理员
  • c_university_1157
  • 枫_0329
  • 傅宣
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧