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