返回

剖析Condition在Java并发编程中的奥秘(下篇)

后端

在上一篇文章中,我们详细探讨了Condition接口的await()方法,了解了线程如何通过它进入等待状态。今天,我们将继续深入Condition的世界,着重剖析signal()signalAll()方法,并结合经典的生产者-消费者模型,展示Condition在实际并发编程中的强大威力。

唤醒沉睡的线程:signal()signalAll()

Condition接口提供了两种唤醒等待线程的方法:signal()signalAll()。它们就像指挥家手中的指挥棒,精准地控制着线程的苏醒。

signal():唤醒一个

signal()方法就像一位细心的观察者,它会从等待队列中挑选出一个线程,轻轻地唤醒它。这个被选中的线程会离开等待队列,加入到锁竞争的队伍中,等待获取锁。一旦获得锁,它便可以继续执行。

值得注意的是,signal()方法只会唤醒一个 线程。如果等待队列中有多个线程,那么其他线程将继续沉睡,等待下一次被唤醒的机会。

signalAll():唤醒所有

signal()的“点对点”唤醒方式不同,signalAll()方法则像一位慷慨的施予者,它会将等待队列中的所有 线程唤醒。这些线程就像听到了集结号,纷纷加入到锁竞争的队伍中,等待获取锁。

生产者-消费者模型:Condition的实战演练

生产者-消费者模型是并发编程中的经典案例,它就像一个忙碌的工厂,生产者不断生产产品,消费者则不断消费产品。为了保证生产和消费的协调,我们需要一个缓冲区来存放产品,并使用Condition来控制生产者和消费者的行为。

代码实现

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumer {

    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private final Object[] buffer = new Object[10];
    private int count = 0;
    private int putIndex = 0;
    private int takeIndex = 0;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == buffer.length) {
                notFull.await(); // 缓冲区满,等待
            }
            buffer[putIndex] = x;
            if (++putIndex == buffer.length) {
                putIndex = 0;
            }
            ++count;
            notEmpty.signal(); // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await(); // 缓冲区空,等待
            }
            Object x = buffer[takeIndex];
            if (++takeIndex == buffer.length) {
                takeIndex = 0;
            }
            --count;
            notFull.signal(); // 唤醒生产者
            return x;
        } finally {
            lock.unlock();
        }
    }
}

代码解读

在上面的代码中,我们使用了ReentrantLock来保证线程安全,并使用Condition来实现生产者和消费者的同步。

  • notFull:表示缓冲区未满的条件。当缓冲区满时,生产者线程会调用notFull.await()进入等待状态,直到消费者线程消费了产品,并调用notFull.signal()唤醒生产者线程。
  • notEmpty:表示缓冲区非空的条件。当缓冲区空时,消费者线程会调用notEmpty.await()进入等待状态,直到生产者线程生产了产品,并调用notEmpty.signal()唤醒消费者线程。

常见问题解答

  1. signal()signalAll()有什么区别?

    signal()只会唤醒一个等待线程,而signalAll()会唤醒所有等待线程。选择哪种方法取决于具体的应用场景。

  2. 为什么需要使用循环来检查条件?

    线程被唤醒后,并不一定能够立即获得锁。因此,需要使用循环来检查条件是否满足,以避免出现错误。

  3. ConditionObject.wait()/notify()有什么区别?

    Condition提供了更灵活的等待/通知机制,可以创建多个条件队列,而Object.wait()/notify()只能使用一个条件队列。

  4. 如何避免死锁?

    避免死锁的关键在于合理地使用锁和条件,确保线程不会无限期地等待。

  5. Condition适用于哪些场景?

    Condition适用于需要更精细地控制线程等待/通知的场景,例如生产者-消费者模型、线程池等。

Condition是Java并发编程中一个强大的工具,它能够帮助我们实现更灵活、更高效的线程同步。通过深入理解signal()signalAll()方法,以及结合实际案例进行分析,我们能够更好地掌握Condition的使用技巧,并在实际项目中发挥它的作用。