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

Posted

技术标签:

【中文标题】在多线程 Java 程序中,每个线程是不是都有自己的 System.out 副本?【英文标题】:In a multithreaded Java program, does each thread have its own copy of System.out?在多线程 Java 程序中,每个线程是否都有自己的 System.out 副本? 【发布时间】:2012-04-18 09:37:16 【问题描述】:

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

【问题讨论】:

你也许可以使用AspectJ返回一个不同的PrintStream对象 【参考方案1】:

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

不,这是不可能的。 System.out 是静态的,每个 JVM 都有一个,在 JVM 最初启动时作为系统类加载器的一部分加载。虽然当然建议使用每个线程正确的日志记录调用,但我认为您不能这样做是有原因的。可能是第 3 方库或其他代码以这种方式使用 System.out

您可以做的一件事(作为一个激进的建议)是创建自己的PrintStream,将其委托给ThreadLocal<PrintStream>。但是您需要 @Override 应用程序调用的适当方法才能使其在每个线程中工作。

最后,如果您因为担心并发而问这个问题,System.outPrintStream,所以它已经是synchronized,可以被多个线程安全使用。

【讨论】:

我目前正在研究命名每个线程,并制作一个采用 Map 的自定义 PrintStream。当自定义的 printstream 接收到将某些内容打印到屏幕的信号时,它使用 Thread.currentThread 获取当前的 Thread 对象并在地图中查找它。如果线程是map中的key,则将输出写入对应的OutputStream,否则进入默认输出。 您可以使用带有适当格式化程序的 java.util.logging(或其他日志框架)来记录线程名称。我强烈推荐这种方法而不是使用 System.out。 我需要根据正在运行的线程将标准输出重定向到变量位置。线程不输出标准的“日志”错误消息。我查看了 Java Logging,但我认为它不适合我的问题。 "System.out 是静态的,每个 ClassLoader 都有一个" 充其量是误导:java.lang.System 类只能由引导类加载器加载(以及所有其他 java.lang 类) . @Gray 可以设置,但是每个JVM只有一个静态变量,不是每个ClassLoader一个。您的陈述在this question 中引起了混乱,这就是我投反对票的原因(如果您能澄清您想说的,我们可以编辑您的答案,我将删除反对票)。【参考方案2】:

System.out 是静态的,因此所有线程之间共享同一个实例。

【讨论】:

【参考方案3】:

你是对的,但不是你想的那样。当一个线程使用

System.out.println();

它需要 reference System.out 的副本,但不是 this 引用的对象的副本。

这意味着所有线程通常会看到相同的对象来写入输出。

注意:此字段不是线程安全的,如果您调用 System.setOut(PrintStream),如果您使用此字段,则可能存在不受欢迎的竞争条件,即不同的线程具有不同的 System.out 本地副本。这不能用来解决这个问题。

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

您可以通过将 System.out 替换为您自己的线程特定实现来完成此操作。即 PrintStream 的子类。我这样做是为了记录我希望每个线程的输出保持一致而不是交错的地方。例如想象一下同时在两个线程中打印两个堆栈跟踪。 ;)

【讨论】:

你可以在启动更多线程之前控制它我的设置,否则你必须希望它没关系。你是什​​么意思Boy this is misleading 只有一个 System.out。您的建议是根据线程更改 System.out,这与拥有多个 System.out 不同。您的建议类似于说拥有 8 个 CD-ROM 和一个 CD-ROM 驱动器相当于拥有 8 个 CD-ROM 驱动器,因为您可以在需要时切换 CD。 CD可以换掉吗?是的。是否与拥有 8 个 CD 驱动器相同?没有。 我建议您将 System.out 更改为在每个线程基础上执行不同操作的组件。这与拥有多个 System.out 值不同。我的评论是关于 System.out 的线程安全性,它允许多个线程由于竞争条件而看到不同的值,这是你不能使用的,只会是一个问题。我建议有一个 8 CD 换碟机,它对用户显示为一个驱动器,例如有 8 个子目录。 @Gray 我已经改写了这个。感谢您的反馈。【参考方案4】:

是否可以基于“每个线程”重定向 System.out

Maia 公司 的一些开发人员提供了 PrintStream 的公共实现,该实现在本文中为每个线程提供一个“STDOUT”:“Thread Specific System.out”。

在他们的实现中,它们仅覆盖写入方法、flush、close 和 checkError。在他们的情况下似乎就足够了。

他们没有需要@Override 所有调用的方法以使其在每个线程中工作”正如@Gray 在他的回答中所说。 p>


注意:

请在下面找到来自 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(); 

【讨论】:

这个链接好像失效了,但是你可以在org.apache.geronimo.gshell.support.gshell-io.SystemOutputHijackerMaven package和source code browser找到类似的实现 @Stephan 您是否有来自 Maia 公司的上述解决方案的工作链接(或访问可下载的 Jar 文件)?正如 ArneBrasseur 所说,原始链接不再有效,该项目看起来很有趣。 @S.O.S 请查看我的回答 (***.com/a/15526391/363573) 更新。【参考方案5】:

是否可以基于“每个线程”重定向 System.out

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

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

【讨论】:

以上是关于在多线程 Java 程序中,每个线程是不是都有自己的 System.out 副本?的主要内容,如果未能解决你的问题,请参考以下文章

Java 线程与操作系统线程

java多线程运行机制

Java内存区域解析

在多线程系统中使用静态 java.sql.Connection 实例是不是安全?

JVM学习记录1--JVM内存布局

谁创建和拥有调用堆栈以及调用堆栈如何在多线程中工作?