Producer/Consumer 在 Java 中。为什么我们需要两个条件?

Producer/Consumer in Java. Why do we need two conditions?

我在 https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html 上看到了这个问题。

文档对这两种情况的描述如下:

We would like to keep waiting put threads and take threads in separate wait-sets so that we can use the optimization of only notifying a single thread at a time when items or spaces become available in the buffer. This can be achieved using two Condition instances.

我可以使用一个条件通知单个线程,因为 signal() 唤醒了一个线程:

class BoundedBuffer2 {
    final Lock lock = new ReentrantLock();
    final Condition condition  = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                condition.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                condition.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            condition.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

为什么我们需要两个条件?

假设您的缓冲区为空,并且有两个线程在 take 上等待,在

上被阻塞
condition.await();

然后一个线程调用put。这将 signal Condition 并唤醒 take 线程之一。该 take 线程随后也将 signal Condition,唤醒另一个 take 线程,该线程将在阻塞状态下循环返回,因为它没有任何东西可以处理。

最后一个 signal 是不必要的。第二个 take 线程不应该被唤醒,因为没有可用的。

两个条件算法允许您只通知那些相关的线程。

设置两个条件的原因是为了区分正在向缓冲区中生成项目的线程和正在使用缓冲区中的项目的线程。

  • ProducingThread 对 notFull 条件感到困扰并负责 notEmpty 条件,这意味着一旦它产生了某些东西,它就必须升起标志并说,'hey, i have produced something, so it is not empty now.'

  • ConsumingThread 对 notEmpty 条件感到困扰,并负责 notFull 条件,这意味着一旦它消耗了一些东西,它就必须举起旗子说,'hey, i have consumed something, so it is not full now.'

通过将这种线程分成两个不同的等待集,我们可以一次提醒一个线程——来自 producerThread 集的生产者线程或来自 consumerThread 集的消费者线程。通过单一条件,我们可能有一个生产者线程来提醒另一个生产者线程,这是不必要的或不相关的。

如果我们只有一个条件,它被两个线程使用,我们将无法区分哪些线程在逻辑上给予机会,例如,3 个生产者线程和 2 个消费者线程正在等待他们的机会 - 或者到 add/remove 项。如果其中一个消费线程完成了它的工作,假设它发出信号,那么另一个消费线程可能会被唤醒,这不会是我们的 objective.

我们可能有 Producer/Consumer 问题的不同用例-

  1. 任务交错 - 我们可以让任务交错 - 例如 - 一个消费,一个生产,一个消费,一个生产等。
  2. 只执行相关任务-只有当生产任务是necessary/possible时才需要唤醒生产线程,同样只有当消费任务是necessary/possible时才需要唤醒消费线程,否则逻辑会变成 irrelevant/broken.