问题描述
我必须使用 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 反映队列中最近发生的事情。