当我使用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!
主要方法的代码:
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大小保持在最低限度,不需要包含额外的包,但主要是因为知道我没有使用任何其他人的库来实现我的目标。
答案 0 :(得分:2)
你有一些竞争条件。
首先,只要stop()
完成,您的程序就会结束。当发生这种情况时,可能是在LogManager线程有机会看到已写入的新字节之前:
cont = false
cont == false
并暂停,然后才有机会写入其字节。此外,您使用baos.toByteArray()
,然后作为单独的操作执行baos.reset()
。如果有人在两个动作之间写了什么,会发生什么?它们不会反映在bytes
变量中,但reset()
会删除它们。
要解决第一个问题,您可以在返回之前进行最后一次检查。换句话说,如果您将整个toByteArray()/ reset()/ println位的重构映射到方法readAndPrint()
,则return
语句将变为:
if (!cont) {
readAndPrint(); // one last read to empty the buffer
return;
}
要解决第二个问题,您应该在toByteArray()
上锁定时执行reset()
和boas
(这也将锁定对该流的写入,因为所有读写操作都是如此in ByteArrayOutputStream是同步的)。这将确保在您执行这两项操作时,没有其他人可以进行写入。
byte[] bytes;
synchronized (baos) {
bytes = baos.toByteArray();
baos.reset();
}
if (bytes.length > ) { ...
此外,您应该将cont
字段设置为volatile,以便在另一个线程中始终可以看到写入。
请注意,上述内容仍会让您对某些比赛持开放态度。例如,如果你有两个“主”线程,你可以想象一个场景,其中一个调用stop()
而另一个仍在尝试打印消息。解决方案是以某种方式协调它,以便在您调用stop()
时,所有线程都已完成日志记录。
多线程是一个非常复杂和微妙的话题,很难通过实验来学习。如果你还没有,我强烈建议你阅读一本书或深入的教程,以便深入了解问题和解决问题的方法。
最后,您没有询问输出中的奇数换行符,但它们可能是因为您正在使用PrintStream进行刷新(因此将其内容写入BAOS)作为信号用于打印前缀,而不是在bytes
缓冲区中查看换行符。如果在写入换行符之前发生了刷新,您将看到您正在看到的行为。