在Java中,Condition
接口是 java.util.concurrent.locks
包下的一部分,它提供了一种更为灵活和强大的线程间通信机制,相较于传统的 Object
监视器方法(如 wait()
、notify()
和 notifyAll()
)。Condition
接口与锁(通常是 ReentrantLock
)一起使用,允许多个条件变量(Condition)与同一个锁关联,从而允许多个线程在不同的条件上等待和唤醒,提高了并发编程的灵活性和效率。
引入 Condition
在Java中,Condition
接口主要用于替代传统的 Object
监视器方法,以提供更精细化的控制。使用 Condition
时,你需要先获取一个锁(如 ReentrantLock
),然后通过这个锁来创建 Condition
对象。每个 Condition
实例管理着那些处于等待状态的线程,因为它们依赖于某个特定的条件。
基本用法
1. 创建锁和条件变量
首先,你需要创建一个锁(例如 ReentrantLock
),然后通过这个锁来创建 Condition
对象。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
2. 等待(Waiting)
当线程需要等待某个条件时,它会先获取锁,然后调用 Condition
对象的 await()
方法。调用 await()
方法后,线程会释放锁并进入等待状态,直到其他线程调用同一个 Condition
对象的 signal()
或 signalAll()
方法将其唤醒。
lock.lock();
try {
// 等待条件满足
while (!conditionMet) {
condition.await(); // 释放锁并进入等待状态
}
// 条件满足后执行的操作
} finally {
lock.unlock(); // 无论是否发生异常,最后都要释放锁
}
注意,使用 while
循环而不是 if
语句来检查条件,这是因为在等待期间条件可能由其他线程多次改变,需要确保只有在条件真正满足时才继续执行。
3. 通知(Signaling)
当条件变量的条件变为满足时,某个线程会调用 Condition
对象的 signal()
或 signalAll()
方法来唤醒一个或所有等待的线程。
signal()
方法唤醒等待该条件变量的一个线程(如果有的话)。signalAll()
方法唤醒等待该条件变量的所有线程。
lock.lock();
try {
// 改变条件变量状态
conditionMet = true;
// 唤醒一个或多个等待的线程
condition.signalAll(); // 示例中使用signalAll,实际使用时根据需求选择signal或signalAll
} finally {
lock.unlock();
}
示例:生产者-消费者问题
为了更好地理解 Condition
的使用,我们可以通过一个经典的生产者-消费者问题来演示。在这个问题中,生产者线程生产产品放入缓冲区,消费者线程从缓冲区中取出产品。我们使用 ReentrantLock
和 Condition
来控制生产和消费之间的同步。
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 ProducerConsumerExample {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 10;
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // 等待缓冲区不满
}
queue.add(value);
System.out.println("Produced: " + value);
notEmpty.signal(); // 唤醒一个等待的消费者
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待缓冲区不为空
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notFull.signal(); // 唤醒一个等待的生产者
} finally {
lock.unlock();
}
}
// 示例主函数,启动生产者和消费者线程
public static void main(String[] args) {
ProducerConsumerExample example = new ProducerConsumerExample();
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
example.produce(i);
Thread.sleep(100); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
while (true) {
example.consume();
Thread.sleep(150); // 模拟消费耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
在这个例子中,我们创建了两个条件变量 notEmpty
和 notFull
,分别用于表示缓冲区不为空和缓冲区不满。生产者线程在尝试向队列中添加元素时会检查队列是否已满,如果已满则等待 notFull
条件变量;消费者线程在尝试从队列中移除元素时会检查队列是否为空,如果为空则等待 notEmpty
条件变量。
总结
Condition
接口提供了一种比传统 Object
监视器方法更灵活和强大的线程间通信机制。通过与 ReentrantLock
一起使用,Condition
允许更精细地控制哪些线程应该被唤醒,以及基于哪些条件进行等待。这种机制在处理复杂的并发问题时非常有用,如生产者-消费者问题、读写锁等。
希望这个详细的解释和示例能帮助你更好地理解如何在Java中使用 Condition
来实现等待和通知机制。如果你在学习或实践中遇到更多问题,不妨访问码小课网站,那里有更多深入的技术文章和实战教程等待你的探索。