JAVA Condition 详解

synchronized 关键字,配合Object的wait()、notify()系列方法可以实现等待/通知模式。对于Lock,通Condition也可以实现等待/通知模式。

Condition时一个接口,其实现类是AQS中的ConditionObject

Lock对象通过newCondition()方法可以获得Condition对象(其实就是ConditionObject)

Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();

Condition的实现分析

ConditionObject类是AQS的内部类,实现了Condition接口

 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

可以看到,该队列和同步队列一样,使用的都是同步器AQS中的节点类Node。同样拥有首节点和尾节点。

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) // clean up if cancelled
 unlinkCancelledWaiters();
 if (interruptMode != 0)
 reportInterruptAfterWait(interruptMode);
}

上述方法的大概过程:

  1. 将当前线程创建为节点,加入等待队列;
  2. 释放锁,唤醒同步队列的后续节点;
  3. while循环判断节点是否因为notify进入同步队列(没有放入则阻塞,继续循环,若中断则退出;若放入,则退出循环,执行后面的判断)

退出while循环说明节点已经在同步队列里,调用 acquireQueued() 方法加入同步状态竞争。

竞争到锁后从 await() 方法返回,即退出该方法

addConditionWaiter方法

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;
}

过程分析:同步队列的首节点移动到等待队列。加入尾节点之前会清除所有状态不为 Condition 的节点。

通知

调用signal()方法可以唤醒等待队列的首节点(等待时间最长),唤醒之前会将节点移动到同步队列

   public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
  1. 首先判断当前线程是否获取了锁
  2. 如果condition队列的第一个节点不入空则调用dosinal方法
private void doSignal(Node first) {
 do {
 if ( (firstWaiter = first.nextWaiter) == null)
 lastWaiter = null;
 first.nextWaiter = null;
 } while (!transferForSignal(first) &&
 (first = firstWaiter) != null);
}

过程:

  1. 首先修改节点
  2. 调用transferForSignal()方法将节点移动到同步队列
final boolean transferForSignal(Node node) {
 //将节点状态变为0 
 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
 return false;
 //将该节点加入同步队列
 Node p = enq(node);
 int ws = p.waitStatus;
 //如果结点p的状态为cancel 或者修改waitStatus失败,则直接唤醒
 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
 LockSupport.unpark(node.thread);
 return true;
}
  1. 调用同步器的enq方法,将节点移动到同步队列,
  2. 满足条件后使用LockSupport方法唤醒该线程

当condition调用signalAll(0方法

public final void signalAll() {
 if (!isHeldExclusively())
 throw new IllegalMonitorStateException();
 Node first = firstWaiter;
 if (first != null)
 doSignalAll(first);
}
private void doSignalAll(Node first) {
 lastWaiter = firstWaiter = null;
 do {
 Node next = first.nextWaiter;
 first.nextWaiter = null;
 transferForSignal(first);
 first = next;
 } while (first != null);
}

诚然可以看到doSignalAll方法使用了do-while循环来唤醒每一个等待队列中的节点,知道first为null,停止循环。

一句话总结 signalAll() 的作用:将等待队列中的全部节点移动到同步队列中,并唤醒每个节点的线程。

总结

整个过程可以分为三步:

第一步:一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到等到队列中,并释放锁。然后再await方法中的一个while循环中来判断节点是否已经再同步队列,是则尝试获取锁,否则一直阻塞

第二步:当线程调用signal方法后,首先检查当前线程是否获取了锁,然后通过doSignal方法将节点移动到同步队列,并唤醒节点中的线程。

第三步:被唤醒的线程,将从await中的while循环中退出来,然后调用acquireQueued方法竞争同步状态。竞争成功则退出await方法,继续执行。

参考:https://www.toutiao.com/a6613221406496784909/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的Java Condition接口使用示例: 假设有一个共享的缓冲区,其中生产者线程可以将数据项添加到缓冲区中,而消费者线程可以从中移除数据项。缓冲区有一个最大容量,当缓冲区已满时,生产者线程需要等待直到缓冲区中有空间可以添加数据。同样,当缓冲区为空时,消费者线程需要等待直到有数据项可供消费。 可以使用JavaCondition接口来实现此类同步。以下是一个示例代码: ```java import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Buffer { private Queue<Integer> buffer; private int maxSize; private Lock lock; private Condition notFull; private Condition notEmpty; public Buffer(int maxSize) { this.maxSize = maxSize; buffer = new LinkedList<>(); lock = new ReentrantLock(); notFull = lock.newCondition(); notEmpty = lock.newCondition(); } public void put(int num) throws InterruptedException { lock.lock(); try { while (buffer.size() == maxSize) { notFull.await(); } buffer.offer(num); System.out.println("Produced " + num); notEmpty.signal(); } finally { lock.unlock(); } } public int take() throws InterruptedException { lock.lock(); try { while (buffer.size() == 0) { notEmpty.await(); } int num = buffer.poll(); System.out.println("Consumed " + num); notFull.signal(); return num; } finally { lock.unlock(); } } } ``` 在这个示例中,我们使用一个有限的缓冲区实现了一个简单的生产者-消费者模型。我们定义了一个缓冲区队列和一个最大容量。我们还使用了Java的Lock和Condition接口来确保线程之间的同步。 在构造函数中,我们初始化了缓冲区,锁和条件变量。我们使用ReentrantLock作为锁,使用newCondition()方法创建了两个条件变量:notFull和notEmpty。 在put()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否已满。如果是,则调用notFull.await()方法将当前线程阻塞,直到缓冲区中有足够的空间可以添加新元素。如果缓冲区未满,则将新元素添加到缓冲区中,并通过notEmpty.signal()方法通知其他可能正在等待的消费者线程可以消费数据。最后,我们释放锁。 在take()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否为空。如果是,则调用notEmpty.await()方法将当前线程阻塞,直到缓冲区中有可消费的元素。如果缓冲区不为空,则从缓冲区中取出一个元素,并通过notFull.signal()方法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值