2017-07-16 52 views
1

當我使用System.out.println打印出我的日誌消息時,我正在編寫一個基本的服務器程序。我寫了一個基本的類文件,用它來寫出日誌。如果我寫:Java PrintStream重定向意外行爲

System.out.println("Hello, world!"); 
System.out.println("Goodbye, world"); 

所需的輸出將是:

Log message - Hello, world! 
Log message - Goodbye, world! 

結束意外事件發生不匹配所需的輸出。相反,它輸出到以下內容。

Log message - Hello, world! 
Goodbye, world! 

主方法的代碼:

public static void main(String[] args){ 
    LogManager.start(); 
    System.out.println("Hello, world!"); 
    System.out.println("Goodbye, world!"); 
    LogManager.stop(); 
} 

類LogManager的切換即打印出來到默認PrintStream,並保持舊的拷貝打印出日誌消息。但是,「日誌消息 - 」並不總是以前綴爲前綴。雖然在每次呼叫println之間睡眠2000毫秒時,輸出如下所示。

Log message - Hello, world! 
Log message - Goodbye, world!Log message - 

LogManager的代碼如下。

import java.io.ByteArrayOutputStream; 
import java.io.OutputStream; 
import java.io.PrintStream; 

public class LogManager implements Runnable{ 

    private final PrintStream ps; 
    private final OutputStream out; 
    private static boolean cont = true; 

    public static void start(){ 
     OutputStream stdout = new ByteArrayOutputStream(); 
     PrintStream ps = new PrintStream(stdout); 
     Thread th = new Thread(new LogManager(System.out, stdout)); 
     System.setOut(ps); 
     th.start(); 
    } 

    public static void stop(){ 
     cont = false; 
    } 

    public LogManager(PrintStream std, OutputStream out){ 
     this.ps = std; 
     this.out = out; 
    } 

    @Override 
    public void run() { 
     ByteArrayOutputStream baos = (ByteArrayOutputStream) out; 
     while(true){ 
      if(!cont) return; 
      byte[] bytes = baos.toByteArray(); 
      if(bytes.length > 0){ 
       baos.reset(); 
       ps.print("Log message - " + new String(bytes)); 
      } 
     } 
    } 
} 

有人請指點我做錯了什麼,非常感謝幫助。我希望遠離圖書館,因爲我希望將JAR的大小保持在最低限度,並且不需要包含額外的軟件包,但主要是爲了知道我沒有使用任何其他軟件庫來實現我正在做的事情。

回答

2

您有一些競賽條件。

首先,只要stop()完成,程序就會結束。當這種情況發生,也可能是LogManager的線程有機會看到已寫入新的字節之前:

  1. 主線程寫道:「再見,世界\ n」
  2. 主線程設置cont = false
  3. 在有機會寫入字節之前,LogManager線程會看到cont == false並暫停。

此外,您使用baos.toByteArray(),然後作爲單獨的行動做baos.reset()。如果有人在兩個操作之間寫入內容會發生什麼?它們不會反映在bytes變量中,但reset()會將其擦除。

要解決第一個問題,您可以在返回之前進行最後一次檢查。換句話說,如果你成像重構是整個toByteArray()/復位()/的println位的方法readAndPrint(),則return聲明變成:

if (!cont) { 
    readAndPrint(); // one last read to empty the buffer 
    return; 
} 

要解決的第二個問題,你應該做的toByteArray()reset()同時在boas(它也將鎖定寫入該流,因爲在ByteArrayOutputStream中的所有讀取和寫入都被同步)鎖定。這將確保沒有其他人可以在執行這兩項操作時進行書寫。

byte[] bytes; 
synchronized (baos) { 
    bytes = baos.toByteArray(); 
    baos.reset(); 
} 
if (bytes.length >) { ... 

另外,你應該讓cont領域的多變,讓一個線程寫在另一個總是看​​到。

請注意,上述情況仍然會讓您對某些比賽開放。例如,如果您有兩個「主要」線程,則可以想象其中一個調用stop()而另一個仍在嘗試打印消息的場景。解決方法是以某種方式進行協調,以便在您撥打stop()時,所有線程都完成了日誌記錄。

多線程是一個非常複雜和微妙的話題,很難通過實驗來學習。如果您還沒有,我強烈建議您閱讀一本書或深入教程,以深入瞭解解決問題的方法和方法。

最後,你沒有問你輸出中的奇數換行符,但它們可能是由於你正在使用PrintStream被刷新(因此將它們的內容寫入BAOS)作爲信號用於打印前綴,而不是像在緩衝區中看到換行符之類的東西。如果該換行發生在換行符寫入之前,您將看到您所看到的行爲。

+0

我試過了,但它似乎產生相同的結果。前綴僅添加到某些日誌中。我相信這可能與緩衝有關。我做了'cont' volatile,並且將循環中的代碼切換到了您所顯示的代碼,但它似乎仍然執行相同的操作。 – Garhoogin

+0

@Garhoogin更新 – yshavit