如何使用 BlockingQueue 解决容量为 1 的生产消费者问题?

问题描述

我必须使用 BlockingQueue 解决生产者-消费者问题。我希望它是 1 个元素的队列。所以解决方案必须是:

生产 1 消耗 1 生产2 消耗2 生产 3 消耗3

但它是:

生产 1 生产2 消耗 1 消耗2 生产 3 生产4等

我目前正在像这样使用 SynchronizedQueue:

class Producent extends Thread {
private final BlockingQueue<Integer> blockingQueue;

public Producent(BlockingQueue<Integer> blockingQueue) {
    this.blockingQueue = blockingQueue;
}

@Override
public void run() {
    for (int i = 0; i < 100; ++i) {
        try {
            System.out.println("PRODUCE: " + i);
            blockingQueue.put(i);
        } catch (InterruptedException e) {
            e.printstacktrace();
        }
    }
}
}

class Konsument extends Thread {
private final BlockingQueue<Integer> blockingQueue;

public Konsument(BlockingQueue<Integer> blockingQueue) {
    this.blockingQueue = blockingQueue;
}

public void run() {
    for (int i = 0; i < 100; ++i) {
        try {
            consume(blockingQueue.take());
        } catch (InterruptedException e) {
            e.printstacktrace();
        }
    }
}

    private void consume(int number) {
        System.out.println("CONSUME: " + number);
    }
}

public class PKmon {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new SynchronousQueue<>();
        Thread producent = new Producent(blockingQueue);
        Thread konsument = new Konsument(blockingQueue);
        producent.start();
        konsument.start();
    }
}

我做错了什么?我也尝试过 ArrayBlockingQueue 和 LinkedBlockingQueue。

解决方法

上下文切换可以发生在任何地方,在这里它们可以防止日志消息被及时写入。假设您的消费者从队列中取出一些东西,通知另一个线程并且它可以在其中放入一些东西,这一切都在消费者可以执行 println 之前。上下文切换发生在队列获取之后和 println 之前。这些方法调用是否属于同一语句的一部分并不重要,队列在其方法调用完成时放弃锁定。

但是你说,“是的,但是另一个线程也导致了一个通知,这不应该导致另一个上下文切换吗?”上下文切换不是强制的,它们取决于操作系统,并且它可以坚持与否。它可能决定要尽量减少开关的数量。

对于生产者来说, println 排在第一位。但是你不需要通知上下文切换,它可以随时发生。

您可以尝试让两个线程在循环内的共享队列上同步:

for (int i = 0; i < 100; i++) {
    synchronized (blockingQueue) {
        consume(blockingQueue.take());
    }
}

(我只展示了一个,但需要更改两个线程才能获得相同的锁。)

这将确保 println 反映队列中最近发生的事情。