【问题标题】:Correct way to System.setErr() for multiple threads?多线程 System.setErr() 的正确方法?
【发布时间】:2014-01-15 13:34:28
【问题描述】:

所以我有一个使用固定线程池来执行一些代码的 Java 应用程序。此代码包括使用输出到 System.err 的第三方库。当我让这段代码执行单线程时,我将 System.err “重定向”到了 PrintStream,最终打印到 log4j 日志。基本上是这样的:

 PrintStream oldErr = System.err;
 System.setErr(new PrintStream(/* custom logging stream here */));

 try
 {
     // do computationally expensive task here
 }
 finally
 {
     System.setErr(oldErr);
 }

按预期工作。输出打印到日志文件而不是控制台,我可以通过更改我的 log4j 配置来完全删除输出。完美。

当我开始添加多线程时,我做了一些研究并遇到了这个 SO 问题:In a multithreaded Java program, does each thread have its own copy of System.out?,这意味着我应该在启动线程池之前执行一次 System.setErr() 并且我已经准备好了.只是情况似乎并非如此。我的代码现在看起来像这样:

 PrintStream oldErr = System.err;
 System.setErr(new PrintStream(/* custom logging stream here */));

 ExecutorService threadPool = Executors.newFixedThreadPool(maxThreadCount);

 try
 {
     // shove computationally expensive task off to a thread
     //   using threadPool.execute()
 }
 finally
 {
     System.setErr(oldErr);
 }

但是,在启动线程池之前调用 System.setErr() 的效果为零。线程都将它们的输出打印到 System.err,就好像我根本没有进行调用一样。呸!

我还尝试让线程在执行它们的任务时调​​用 System.setErr(),但是有一些明显的竞争条件问题 - 间歇性输出到控制台,以及一般的肮脏感。在一次测试运行中,它甚至看起来像是死锁了。

我错过了一些简单的东西吗? FWIW,这是我的 JVM 详细信息:

java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b07)
Java HotSpot(TM) 64-Bit Server VM (build 17.0-b17, mixed mode)

谢谢。

编辑:我的问题基本上已通过接受的答案解决,但为了完整起见,我想补充一点,我无法使用 Futures 和 get() 解决我的特殊情况。我的个人任务消耗大量 RAM,但持续时间各不相同,因此我不得不使用Java: ExecutorService that blocks on submission after a certain queue size 的答案中概述的小型阻塞队列。我基本上陷入了认为newFixedThreadPool() 中的默认设置适用于我的情况的陷阱,而实际上它们并没有,而布赖恩的回答有助于揭露这些错误的假设。谢谢布赖恩!

【问题讨论】:

  • 我怀疑问题在于System 流上的final 关键字的含义与在整个Java 其余部分中的含义不同,并且它有自己的“写保护”语义。您可能会收到有关启动线程池的指令重新排序。尝试在调用setErr 和初始化线程池之间放入一个无用的volatile Object 类变量并调用它的hashCode() 方法。
  • @chrylis 实际上,System 中的 final 与 java 的其余部分具有相同的语义:stackoverflow.com/a/3301720/2299084
  • 听起来你需要在每个线程中重定向System.err,并使/* custom logging stream here */线程安全。
  • @chrylis 谢谢,我正在尝试那个“技巧”,但由于某种原因,当我尝试使用它时,我的程序会立即退出。没有消息,退出代码 1,也许消息被吞了,因为我重定向了 System.err :)。可惜我得走了,所以我明天再试试。
  • @user60561 No, it doesn't.

标签: java multithreading io outputstream


【解决方案1】:

以下 SSCCE 完全符合您的要求:

public class App
{
    public static void main(String[] args) throws FileNotFoundException, InterruptedException, ExecutionException 
    {
        PrintStream oldErr = System.err;
        System.setErr(new PrintStream(new File("test")));

        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        List<Future<?>> fList = new LinkedList<Future<?>>();
        for (int i = 0; i < 5; i++)
        {
            fList.add(threadPool.submit(new Runnable() {

                @Override
                public void run()
                {
                    System.err.println("This is to the err stream");
                }

            }));
        }

        for (Future<?> f : fList)
        {
            f.get();
        }

        threadPool.shutdown();

        System.setErr(oldErr);
        System.err.println("This is to the err stream");
    }
}

运行此示例后,文件“test”包含 5 行,每行都说“这是到 err 流”,最后在控制台上打印了一行。

您显示的代码 sn-p 在 finally 块中调用 System.setErr(oldErr); ...您是否等待在该 try 块中完成所有任务?

如果不是,它会解释您在 cmets 中所做的声明,即 “我会看到输出片段打印到 System.err 并且片段打印到我的日志文件” ...这几乎就是您的意思会发生的。 .submit() 是一个非阻塞调用,返回一个Future;您正在运行任务时重置流。

【讨论】:

  • 我的 sn-p 没有显示的是对 threadPool.shutdown() 的调用。我认为该调用使主线程阻塞,直到任务完成,但我现在意识到它没有。我已经修改了我的代码以在重置System.err 之前同时调用threadPool.shutdown()threadPool.awaitTermination() 以避免该问题。您的回答确实为我指明了正确的方向,谢谢!
猜你喜欢
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多