【问题标题】:Java PrintStream redirection behaving unexpectedlyJava PrintStream 重定向行为异常
【发布时间】:2017-07-16 02:52:23
【问题描述】:

当我使用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!

main方法的代码:

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 调用之间休眠 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 大小保持在最低限度,而不必包含额外的包,尽管主要是因为我知道我没有使用任何其他人的图书馆来实现我正在做的事情。

【问题讨论】:

    标签: java multithreading logging printstream


    【解决方案1】:

    你有几个竞争条件。

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

    1. 主线程写“再见,世界\n”
    2. 主线程集cont = false
    3. Lo​​gManager 线程看到 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(并因此将其内容写入 BAOS)作为信号用于打印前缀,而不是在bytes 缓冲区中看到换行符。如果刷新发生在换行符被写入之前,您将看到您所看到的行为。

    【讨论】:

    • 我试过这个,但它似乎产生了相同的结果。前缀仅添加到某些日志中。我相信这可能与缓冲有关。我使cont volatile 并将循环中的代码切换到您显示的代码,但它似乎仍然在做同样的事情。
    • @Garhoogin 更新
    猜你喜欢
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多