我正在编写一个多线程Java程序,其中每个线程可能需要将其标准输出重定向到单独的文件。每个线程都有自己的文件。可以在“每个线程”的基础上重定向System.out,还是可以跨所有线程更改System.out全局?
答案 0 :(得分:23)
是否可以在“每个线程”的基础上重定向System.out
不,不可能。 System.out
是静态的,当JVM最初启动时,每个JVM都有一个作为系统类加载器的一部分加载。虽然建议每个线程使用正确的日志记录调用,但我认为有理由不能这样做。可能是第三方库或其他代码就是以这种方式使用System.out
。
你可以做的一件事(作为一个激进的建议)就是让自己的PrintStream
委托给ThreadLocal<PrintStream>
。但是,您需要@Override
应用程序调用的适当方法才能使其按线程运行。
最后,如果你因为担心并发而问这个问题,那么System.out
就是PrintStream
,所以它已经是synchronized
,可以被多个线程安全地使用。
答案 1 :(得分:8)
是否可以在“每个线程”的基础上重定向System.out
Maia Company 的一些开发人员提供了一个PrintStream的公共实现,在本文中每个线程提供一个“ STDOUT ”:“Thread Specific System.out”。
在它们的实现中,它们只覆盖write方法,flush,close和checkError。在他们的案例中似乎已经足够了。
他们没有“需要@Override所有调用的方法,让每个线程都能使用”作为@Gray在他的回答中说明。
<强> NOTA:强>
请在下面找到Maia的原始代码。
我在回程机器上发现了它here。原始页面已从Maia网站上删除。我在这里重现它是为了读者的好奇心。 我不对此代码提供任何支持。
<强> Main.java 强>
创建一个ThreadPrintStream,将其安装为System.out,并创建并启动10个线程。
public class Main {
public static void main(String[] args) {
// Call replaceSystemOut which replaces the
// normal System.out with a ThreadPrintStream.
ThreadPrintStream.replaceSystemOut();
// Create and start 10 different threads. Each thread
// will create its own PrintStream and install it into
// the ThreadPrintStream and then write three messages
// to System.out.
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new StreamText());
thread.start();
// Report to the console that a new thread was started.
System.out.println("Created and started " + thread.getName());
}
}
}
<强> StreamText.java 强>
每个线程的简单Runnable,用于打开线程输出的文件并将其安装到ThreadPrintStream中。
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
/** A small test class that sets System.out for the currently executing
* thread to a text file and writes three messages to System.out. */
public class StreamText implements Runnable {
@Override
public void run() {
try {
// Create a text file where System.out.println()
// will send its data for this thread.
String name = Thread.currentThread().getName();
FileOutputStream fos = new FileOutputStream(name + ".txt");
// Create a PrintStream that will write to the new file.
PrintStream stream = new PrintStream(new BufferedOutputStream(fos));
// Install the PrintStream to be used as System.out for this thread.
((ThreadPrintStream)System.out).setThreadOut(stream);
// Output three messages to System.out.
System.out.println(name + ": first message");
System.out.println("This is the second message from " + name);
System.out.println(name + ": 3rd message");
// Close System.out for this thread which will
// flush and close this thread's text file.
System.out.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
<强> ThreadPrintStream.java 强>
扩展java.io.PrintStream。 ThreadPrintStream的一个对象替换了普通的System.out,并为每个线程维护一个单独的java.io.PrintStream。
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
/** A ThreadPrintStream replaces the normal System.out and ensures
* that output to System.out goes to a different PrintStream for
* each thread. It does this by using ThreadLocal to maintain a
* PrintStream for each thread. */
public class ThreadPrintStream extends PrintStream {
/** Changes System.out to a ThreadPrintStream which will
* send output to a separate file for each thread. */
public static void replaceSystemOut() {
// Save the existing System.out
PrintStream console = System.out;
// Create a ThreadPrintStream and install it as System.out
ThreadPrintStream threadOut = new ThreadPrintStream();
System.setOut(threadOut);
// Use the original System.out as the current thread's System.out
threadOut.setThreadOut(console);
}
/** Thread specific storage to hold a PrintStream for each thread */
private ThreadLocal<PrintStream> out;
private ThreadPrintStream() {
super(new ByteArrayOutputStream(0));
out = new ThreadLocal<PrintStream>();
}
/** Sets the PrintStream for the currently executing thread. */
public void setThreadOut(PrintStream out) {
this.out.set(out);
}
/** Returns the PrintStream for the currently executing thread. */
public PrintStream getThreadOut() {
return this.out.get();
}
@Override public boolean checkError() {
return getThreadOut().checkError();
}
@Override public void write(byte[] buf, int off, int len) {
getThreadOut().write(buf, off, len);
}
@Override public void write(int b) { getThreadOut().write(b); }
@Override public void flush() { getThreadOut().flush(); }
@Override public void close() { getThreadOut().close(); }
}
答案 2 :(得分:4)
你是对的但不是你想的那样。当线程使用
时System.out.println();
它需要引用 System.out
的副本,但不是此引用的对象的副本。
这意味着所有线程通常会看到相同的对象以写入输出。
注意:此字段不是线程安全的,如果您调用System.setOut(PrintStream)
如果使用此字段,则存在潜在的,不合需要的竞争条件,其中不同的线程具有不同的System.out本地副本。这不能用来解决这个问题。
是否可以在“每个线程”的基础上重定向System.out
您可以通过将System.out替换为您自己的特定于线程的实现来完成此操作。即PrintStream的子类。我这样做是为了记录我希望每个线程的输出是一致的而不是交错的。例如想象一下,在两个线程中同时打印两个堆栈跟踪。 ;)
答案 3 :(得分:3)
System.out
是静态的,因此所有线程之间共享相同的实例。
答案 4 :(得分:0)