Java PrintStream重定向行为异常

Java PrintStream重定向行为异常,java,multithreading,logging,printstream,Java,Multithreading,Logging,Printstream,当我使用System.out.println打印日志消息时,我正在编写一个基本的服务器程序。我编写了一个基本类文件,用它来写日志。如果我写下以下内容: System.out.println("Hello, world!"); System.out.println("Goodbye, world"); 所需的输出将是: Log message - Hello, world! Log message - Goodbye, world! 最终发生的事情与期望的输出不匹配。相反,它输出到以下对象 L

当我使用
System.out.println
打印日志消息时,我正在编写一个基本的服务器程序。我编写了一个基本类文件,用它来写日志。如果我写下以下内容:

System.out.println("Hello, world!");
System.out.println("Goodbye, world");
所需的输出将是:

Log message - Hello, world!
Log message - Goodbye, world!
最终发生的事情与期望的输出不匹配。相反,它输出到以下对象

Log message - Hello, world!
Goodbye, world!
Log message - Hello, world!
Log message - Goodbye, world!Log message - 
主要方法的代码:

public static void main(String[] args){
    LogManager.start();
    System.out.println("Hello, world!");
    System.out.println("Goodbye, world!");
    LogManager.stop();
}
类LogManager切换打印到的默认
PrintStream
,并保留一份旧的副本以打印日志消息。但是,“Log message-”并不总是有前缀。尽管如此,在每次
println
调用之间休眠2000ms时,输出如下所示

Log message - Hello, world!
Goodbye, world!
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大小保持在最小,不需要额外的包,尽管主要是为了知道我没有使用任何其他人的库来实现我正在做的事情。

您有一些竞争条件

首先,只要
stop()
完成,程序就会结束。发生这种情况时,可能是在LogManager线程有机会看到已写入的新字节之前:

  • 主线程写入“再见,世界\n”
  • 主线程设置
    cont=false
  • LogManager线程看到
    cont==false
    并在有机会写入其字节之前停止
  • 另外,使用
    baos.toByteArray()
    ,然后作为一个单独的操作执行
    baos.reset()
    。如果有人在两个动作之间写了什么,会发生什么?它们不会反映在
    bytes
    变量中,但是
    reset()
    会删除它们

    要解决第一个问题,您可以在返回之前进行最后一次检查。换句话说,如果将整个toByteArray()/reset()/println位重构为方法
    readAndPrint()
    ,则
    return
    语句变为:

    if (!cont) {
        readAndPrint(); // one last read to empty the buffer
        return;
    } 
    
    要解决第二个问题,您应该在锁定
    boas
    的同时执行
    toByteArray()
    reset()
    (这也会锁定对该流的写入,因为ByteArrayOutputStream中的所有读写都是同步的)。这将确保在您执行这两个操作时,没有其他人可以进行写入

    byte[] bytes;
    synchronized (baos) {
        bytes = baos.toByteArray();
        baos.reset();
    }
    if (bytes.length > ) { ...
    
    此外,您应该使
    cont
    字段不稳定,以便在一个线程中写入总是在另一个线程中看到

    请注意,上述情况仍然会让您有机会参加某些比赛。例如,如果您有两个“主”线程,您可以想象这样一种场景,其中一个线程调用
    stop()
    ,而另一个线程仍在尝试打印消息。解决方案是以某种方式协调它,以便在调用
    stop()
    时,所有线程都已完成其日志记录

    多线程是一个非常复杂和微妙的话题,很难通过实验来学习。如果您还没有,我强烈建议您阅读一本书或深入的教程,以便更好地掌握问题和解决问题的方法


    最后,您没有询问输出中的奇数换行符,但它们可能是因为您使用正在刷新的PrintStream(并将其内容写入BAS)作为打印前缀的信号,而不是在
    字节
    缓冲区中看到换行符。如果换行符在换行符写入之前出现刷新,您将看到您看到的行为。

    我尝试过这个方法,但它似乎产生了相同的结果。前缀仅添加到某些日志中。我相信这可能与缓冲有关。我将
    cont
    设置为volatile,并将循环中的代码切换到您显示的代码,但它似乎仍在执行相同的操作。@Garhoogin更新