简体   繁体   中英

Synchronizing on System.out

I changed System.out to print to a file, by invoking System.setOut and System.setErr .

Every night at midnight, we want to rename (archive) the current log file, and create a new one.

if (out != null) {
    out.close();
    out = null;
    File f = new File(outputfilename);
    f.renameTo(new File(dir.getPath().replace(".log", "-" + System.currentTimeMillis() + ".log")))
    StartLogFile();
}

The StartLogFile() :

if (out == null) {
    out = new FileOutputStream(outputfilename, true);
    System.setOut(new PrintStream(out));
    System.setErr(new PrintStream(out));
}

I've left exception-handling out.

My concern is that if something tries to print in between out.close() and setOut / setErr that I'm going to miss a log.

My real question is, how can I make this atomic with other calls to System.out.println ? I was thinking about trying

synchronized (System.out) {

}

but I'm not actually sure if the intrinsic lock here does anything. Especially since I'm nullifying the out object during the operation.

Does anyone know how I can ensure proper synchronization here?

I would create the new out before closing the old one:

PrintStream old = System.out;
out = new FileOutputStream(outputfilename, true);
System.setOut(new PrintStream(out));
old.close();

This way the old PrintStream is not closed until the new one is created and assigned. At all times there is a valid PrintStream in System.out.

There is no need for synchronized block, because everything is in the same thread.

Yes you can achieve proper synchronization that way. Here is a sample test.

@Test
public void test() throws InterruptedException {

    new Thread(()->{
        while(true){
            System.out.println("printing something");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }).start();

    Thread.sleep(500);
    synchronized (System.out){
        System.out.println("changin system out");
        Thread.sleep(2000);
        System.out.println("finished with sysout");
    }
    Thread.sleep(2000);
}

and the output will be:

printing something
printing something
printing something
printing something
printing something
changin system out
finished with sysout
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something
printing something

There is no way to make this work safely , since you have no control what the calling code is doing with System.out. Think of this:

public void doSomethingTakingALongTime(PrintStream target) {
    // lots of code
}

// somewhere else
doSomethingTakingALongTime(System.out);

You can never be sure there isn't a copy of System.out reference somewhere out there in a local variable or method parameter.

The proper way to solve this would be to set System.out only once , at the very start of the program, and instead of using a standard PrintStream, you use your own implementation that delegates everything to the current target.

You are then in complete control of every output made through System.out and can synchronize at you leisure where required. If your own implementation synchronizes every operation, the question of what happens while you're changing the logging target doesn't even arise - every other caller will simply be blocked by the synchronization lock.

Btw. its questionable to use System.out for logging. The de-facto standard for logging would be using log4j. Consider switching to that.

Edit: Actually implementing this delegation can be rather easy . There is a constructor PrintStream(OutputStream). That means you can just implement delegation in an OutputStream (that has considerably less methods than PrintStream) and set System.out to your new PrintStream(YourRetargettingOutputStream).

You can define an object explicitly for locking like

static final Object lock = new Object();

How about locking over it like below

synchronized(lock){
    if(out != null) {
        out.close();
        out = null;
        File f = new File(outputfilename);
        f.renameTo(new File(dir.getPath().replace(".log", "-" + System.currentTimeMillis() + ".log")))
        StartLogFile();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM