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(); }
}