Thread Specific System.out
We recently needed a multi-threaded Java program where each thread
would write its output to a separate text file. Some of the code that
each thread would execute was already written and writing its output to
System.out
. Our management did not want us to take the time
to rewite the existing code, so we wrote a class named
ThreadPrintStream
that extends
java.io.PrintStream
and maintains a separate
PrintStream
for each thread using ThreadLocal
.
Then we used an object of ThreadPrintStream
to replace the
normal System.out
.
Below you will find three classes:
- Main
- Creates a
ThreadPrintStream
, installs it as
System.out
, and creates and starts 10 threads.
- StreamText
- a simple
Runnable
for each thread that needs to
write to its own System.out
- ThreadPrintStream
- extends
java.io.PrintStream
. An object of
ThreadPrintStream
replaces the normal
System.out
and maintains a separate
java.io.PrintStream
for each thread.
Main |
|
+ main(args : String[]) : void |
|
|
java.io.PrintStream |
|
+ PrintStream(file : File)
+ checkError() : boolean
+ write(buf : byte[], offset : int, len : int) : void
+ write(b : int) : void
+ flush() : void
+ close() : void
|
|
|
«interface» java.lang.Runnable |
+ run() : void |
|
△ |
△ |
╎ |
| |
StreamText |
|
+ run() : void |
|
ThreadPrintStream |
− out : ThreadLocal<PrintStream> |
+ replaceSystemOut() : void
− ThreadPrintStream(file : File)
+ createThreadOut() : void
− setThreadOut(out : PrintStream) : void
− getThreadOut() : PrintStream
+ checkError() : boolean
+ write(buf : byte[], offset : int, len : int) : void
+ write(b : int) : void
+ flush() : void
+ close() : void
|
|
Main
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
/** 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 {
((ThreadPrintStream)System.out).createThreadOut();
// Output three messages to System.out.
String name = Thread.currentThread().getName();
System.out.println(name + ": first message");
System.out.println("This is the second message from " + name);
System.out.println(name + ": third 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
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
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;
/** Default and only constructor */
private ThreadPrintStream() {
// We must call at least one constructor in the parent
// class. The one that takes an OutputStream as a parameter
// seems the least resource intensive, so we call that one.
super(new ByteArrayOutputStream(0));
out = new ThreadLocal<PrintStream>();
}
/** Create and open a text file where System.out.println()
* will send its data for the current thread. */
public void createThreadOut() throws FileNotFoundException {
// 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));
setThreadOut(stream);
}
/** Sets the PrintStream for the
* currently executing thread. */
private void setThreadOut(PrintStream out) {
this.out.set(out);
}
/** Returns the PrintStream for the
* currently executing thread. */
private 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(); }
}