私はマルチスレッドのJavaプログラムを書いています。このプログラムでは、各スレッドが標準出力を個別のファイルにリダイレクトする必要がある可能性があります。各スレッドには独自のファイルがあります。System.outを「スレッドごと」にリダイレクトすることは可能ですか、それともSystem.outへの変更はすべてのスレッドにわたってグローバルですか?
5 に答える
System.outを「スレッドごと」にリダイレクトすることは可能ですか?
いいえ、できません。 System.out
は静的であり、JVMが最初に起動するときにシステムクラスローダーの一部としてロードされるJVMごとに1つあります。もちろん、スレッドごとに適切なロギング呼び出しを使用することをお勧めしますが、これを実行できない理由があると思います。おそらく、サードパーティのライブラリまたは他のコードがSystem.out
この方法で使用されています。
(根本的な提案として)できることの1つは、PrintStream
に委任する独自のものを作成することThreadLocal<PrintStream>
です。@Override
ただし、スレッドごとに機能させるには、アプリケーションによって呼び出される適切なメソッドを使用する必要があります。
System.out
最後に、並行性が心配なためにこれを求めている場合は、PrintStream
すでにsynchronized
カバーされており、複数のスレッドで安全に使用できます。
System.outを「スレッドごと」にリダイレクトすることは可能ですか?
Maia Companyの一部の開発者は、この記事のスレッドごとに1つの「 STDOUT 」を提供するPrintStreamのパブリック実装を提供しています:「 ThreadSpecificSystem.out」。
それらの実装では、writeメソッド、flush、close、およびcheckErrorのみをオーバーライドします。彼らの場合はそれで十分のようです。
@Grayが彼の回答で述べたように、「スレッドごとに機能させるために呼び出されるすべてのメソッドを@Overrideする必要はありません」 。
注:
Maiaの元のコードを以下に示します。
私はここでウェイバックマシンでそれを見つけました。元のページは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
スレッドの出力用のファイルを開き、それをThreadPrintStreamにインストールする、スレッドごとの単純なRunnable。
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(); }
}
あなたは正しいですが、あなたが考える方法ではありません。スレッドが使用する場合
System.out.println();
参照 のコピーを取りますが、System.out
これが参照するオブジェクトのコピーは取りません。
これは、通常、すべてのスレッドが出力に書き込むために同じオブジェクトを参照することを意味します。
注:このフィールドはスレッドセーフではありません。これSystem.setOut(PrintStream)
を使用すると、異なるスレッドが異なるローカルコピーのSystem.outを持つ潜在的な望ましくない競合状態が発生します。これを使用してこの質問を解決することはできません。
System.outを「スレッドごと」にリダイレクトすることは可能ですか?
これを行うには、System.outをスレッド固有の独自の実装に置き換えます。つまり、PrintStreamのサブクラスです。これは、各スレッドの出力に一貫性を持たせ、インターリーブしないようにしたいログ記録のために行いました。たとえば、2つのスタックトレースを2つのスレッドで同時に印刷することを想像してみてください。;)
System.out
は静的であるため、同じインスタンスがすべてのスレッド間で共有されます。
System.outを「スレッドごと」にリダイレクトすることは可能ですか?
それらすべてを、「スレッドごと」のロジックを担当するデリゲートにリダイレクトできます。
これは、独自のファイル出力を持つ並列JBehaveテストの例です。