【问题标题】:Producer/Consumer in Java. Why do we need two conditions?Java中的生产者/消费者。为什么我们需要两个条件?
【发布时间】:2016-08-01 03:08:22
【问题描述】:

我在https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html 上阅读了有关该问题的信息。

这是文档中关于这两种情况的说明:

我们希望继续等待 put 线程和 take 线程分开 等待集,这样我们就可以使用只通知一个的优化 当项目或空间在 缓冲。这可以使用两个 Condition 实例来实现。

我可以使用一个条件通知单个线程,因为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();
        }
    }
}

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

【问题讨论】:

    标签: java multithreading synchronization producer-consumer java.util.concurrent


    【解决方案1】:

    假设你的缓冲区是空的,两个线程正在等待take,被阻塞了

    condition.await();
    

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

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

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

    【讨论】:

    • 为什么文档中的示例不使用signalAll() 而不是signal(),因为只有一种类型的线程在一个条件下等待,它们在这里是否等效? signalAll 仅在生产者/消费者使用相同条件的情况下才需要?
    • @SumitJain 不,这不是有多少条件的问题。更多的是关于一旦唤醒有多少线程可以取得进展。在这个例子中,在notFull.await() 上的put 中可以有多个线程在等待,但是一旦它被唤醒,只有一个线程可以取得进展(因为队列中只有一个位置可以添加)。
    • 同意,但是如果我们使用一个条件,那么我们将不得不使用 signalAll 否则使用信号它也可以唤醒相同类型的线程,例如如果从take()调用,它可以唤醒另一个调用take()的线程
    • @SumitJain 从这个意义上说,当然。 take 线程将无法继续(因为循环条件检查),但不会有更多信号来唤醒 put 线程。
    【解决方案2】:

    这两种情况的原因是为了区分向缓冲区生成项目的线程和从缓冲区消费项目的线程。

    • ProducingThread 为 notFull 条件而烦恼,并负责 notEmpty 条件,这意味着一旦它产生了一些东西,它就必须举旗说,'嘿,我已经生产了一些东西,所以它现在不是空的。 '

    • ConsumingThread 对 notEmpty 条件感到困扰并负责 notFull 条件,这意味着一旦它消耗了一些东西,它就必须举旗说,'嘿,我已经消耗了一些东西,所以它现在还没有满。 '

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

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

    我们可能有不同的生产者/消费者问题用例-

    1. 任务交错 - 我们可以让任务交错 - 例如 - 一消费、一生产、一消费、一生产等。
    2. 仅执行相关任务 - 仅当生产任务是必要/可能时才需要唤醒生产线程,类似地,仅当消费任务是必要/可能时才需要唤醒消费线程,否则逻辑将变得不相关/中断。

    【讨论】:

    • 3个生产者线程和2个消费者线程如何同时等待机会?
    • 假设您有 4 个生产者线程和 2 个消费者线程,例如,缓冲区被 1 个生产者线程使用,那么其余线程正在等待使用缓冲区的机会。
    • @MaksimDmitriev,在一个突发应用程序中,一个或多个生产者可能在轮到其中一个消费者采取行动之前完全填满队列。同样,一个或多个消费者有可能在一个生产者有机会放东西之前完全排空队列。无论哪种方式,您都可以让生产者和消费者同时等待。
    猜你喜欢
    • 2019-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多