首先爲了防止標記問題重複由不喜歡閱讀到最後的傢伙我已閱讀Producer-Consumer Logging service with Unreliable way to shutdown 問題。但它不能完全回答與書中文本相矛盾的問題和答案。爲什麼LogWriter中的競爭條件會導致生產者阻塞? [實踐中的併發]
在書中提供了下面的代碼:
public class LogWriter {
private final BlockingQueue<String> queue;
private final LoggerThread logger;
private static final int CAPACITY = 1000;
public LogWriter(Writer writer) {
this.queue = new LinkedBlockingQueue<String>(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");
}
和評論這樣說:
關停日誌寫的另一種方法是設置標誌以防止進一步的消息 「關閉請求」提交了 ,如代碼清單7.14所示。然後消費者可以在收到關機請求通知後,排隊 , 寫出任何待處理消息並解除阻止日誌中的所有生產者 。但是,這種方法有競爭條件,使其不可靠 。日誌的執行是一個check-then-act序列: 生產者可以觀察到該服務尚未關閉 但仍然在關閉後對消息進行排隊,同樣有生產者可能在日誌中被阻止的風險,並且從未變得暢通無阻。 有一些技巧可以降低這種可能性(比如在宣佈排隊隊列之前讓消費者等待幾秒鐘),但這些並不會改變根本性問題,僅僅是可能導致失敗的可能性 。
這句話對我來說足夠難了。
我明白
if(!shutdownRequested)
queue.put(msg);
不是原子和消息可以shutdowning之後添加到所述隊列。是的,這不是很準確,但我沒有看到問題。隊列就會流失,當隊列將空時,我們可以停止LoggerThread。尤其是我不明白爲什麼生產者可以封鎖。
作者沒有提供完整的代碼,因此我無法理解所有細節。我相信這本書是由社區大多數人閱讀的,這個例子有詳細的解釋。
請用完整的代碼示例解釋。
似乎相當牽強,期望1000線程在相同的兩行代碼之間等待。雖然我意識到如果理論上可行的話,它在技術上仍然是不正確的。 – shmosel
我很感興趣,看看您是否可以演示關閉過程如何實際排空隊列*和所有阻止的生產者*。從我的測試中,'drainTo()'只消耗已排隊的項目。 – shmosel
@shmosel我同意。這有點牽強,但我認爲你誤解了我的答案。 1000線程不會等待'put'。這是第1001線正在等待。對於'put'來阻止,你需要隊列中有1000個元素,並且第1001個塊試圖「放入」一個新元素。所有這一切,同時確保消費者不會爲第1001個元素騰出空間,只有當1001個線程嘗試在隊列中放入新元素時,消費者完成排隊並現在正在消失while循環。我沒有看到任何其他解釋該段的方式!你做? – CKing