为什么LogWriter中的竞争条件会导致生产者阻塞?

首先要防止标记问题由不喜欢读到最后的人重复我已经阅读了生产者 – 消费者日志服务,并且关闭问题的方式不可靠 。 但它没有完全回答问题和答案与书中的文字相矛盾。

在书中提供以下代码:

public class LogWriter { private final BlockingQueue queue; private final LoggerThread logger; private static final int CAPACITY = 1000; public LogWriter(Writer writer) { this.queue = new LinkedBlockingQueue(CAPACITY); this.logger = new LoggerThread(writer); } public void start() { logger.start(); } public void log(String msg) throws InterruptedException { queue.put(msg); } private class LoggerThread extends Thread { private final PrintWriter writer; public LoggerThread(Writer writer) { this.writer = new PrintWriter(writer, true); // autoflush } public void run() { try { while (true) writer.println(queue.take()); } catch (InterruptedException ignored) { } finally { writer.close(); } } } } 

现在我们应该了解如何停止这个过程。 我们应该停止记录,但不应该跳过已经提交的消息。

作者研究方法:

 public void log(String msg) throws InterruptedException { if(!shutdownRequested) queue.put(msg); else throw new IllegalArgumentException("logger is shut down"); } 

并像这样评论:

关闭LogWriter的另一种方法是设置“shutdown requested”标志以防止提交更多消息,如代码清单7.14所示。 然后,消息者可以在收到请求关闭的通知后排空队列,写出任何待处理的消息并解除阻止在日志中阻止的任何生产者。 然而,这种方法具有使其不可靠的竞争条件。 log的实现是一个check-then-act序列:生产者可以观察到服务尚未关闭但仍然在关闭后排队消息,同样存在生产者可能在日志中被阻塞而永远不会被解除阻塞的风险。 有一些技巧可以降低这种可能性(就像让消费者在宣布队列耗尽之前等待几秒钟),但这些并没有改变根本问题,只会导致失败的可能性。

这句话对我来说足够困难。

我明白那个

  if(!shutdownRequested) queue.put(msg); 

不是primefaces的,并且可以在关闭后将消息添加到队列中。 是的,它不是很准确,但我没有看到问题。 队列刚刚耗尽,当队列为空时我们可以停止LoggerThread。 特别是我不明白为什么生产者可以被阻止

作者没有提供完整的代码,因此我无法理解所有细节。 我相信这本书是由社区大多数人阅读的,这个例子有详细的解释。

请用完整的代码示例解释。

首先要理解的是,当请求关闭时,生产者需要停止接受任何更多请求,而消费者(在这种情况下为LoggerThread )需要耗尽队列。 您在问题中提供的代码仅展示了故事的一个方面; 当shutdownRequestedtrue时,生产者拒绝任何进一步的请求。 在这个例子之后,作者继续说:

然后,消费者可以在被通知已经请求关闭时排空队列,写出任何未决消息并解除阻塞在日志中阻塞的任何生产者

首先,如你的问题所示, queue.take中的LoggerThread将无限制地阻塞队列中可用的新消息; 但是,如果我们想要关闭LoggerThread (正常),我们需要确保当shutdownRequested为true而不是无限地被queue.take阻塞时, queue.take中的关闭代码有机会执行。

当作者说消费者可以排空队列时,他的意思是LogWritter可以检查shutdownRequested ,如果它是真的,它可以调用非阻塞的drainTo方法在一个单独的集合中排除队列的当前内容而不是调用queue.take (或调用类似的非阻塞方法)。 备用,如果shutdownRequested为false, LogWriter可以像往常一样继续调用queue.take

这种方法的真正问题在于实现了log方法(由生产者调用)的方式。 由于它不是primefaces的,因此多个线程可能会错过shutdownRequested设置为true。 如果错过此更新的线程数大于queueCAPACITY ,会发生什么情况。 让我们再看一下log方法。 (为了便于解释,增加了花括号):

 public void log(String msg) throws InterruptedException { if(!shutdownRequested) {//A. 1001 threads see shutdownRequested as false and pass the if condition. //B. At this point, shutdownRequested is set to true by client code //C. Meanwhile, the LoggerThread which is the consumer sees that shutdownRequested is true and calls //queue.drainTo to drain all existing messages in the queue instead of `queue.take`. //D. Producers insert the new message into the queue. queue.put(msg);//Step E } else throw new IllegalArgumentException("logger is shut down"); } } 

步骤E所示,当LoggerThread完成排空队列并退出w时,多个生产者线程可以调用put 。 在第1000个线程调用之前应该没有问题。 真正的问题是第1001个线程调用时。 它将阻塞,因为队列容量仅为1000并且LoggerThread可能不再存活或订阅queue.take方法。