在多线程Java程序中,每个线程都有自己的System.out副本吗?

时间:2012-04-04 16:16:50

标签: java multithreading io outputstream

我正在编写一个多线程Java程序,其中每个线程可能需要将其标准输出重定向到单独的文件。每个线程都有自己的文件。可以在“每个线程”的基础上重定向System.out,还是可以跨所有线程更改System.out全局?

5 个答案:

答案 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)

  

是否可以在“每个线程”的基础上重定向System.out

您可以将它们全部重定向到您的委托,该委托将负责“每线程”逻辑。

Here is具有自己文件输出的并行JBehave测试示例。