2017-07-16 51 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