131

I'm trying to make sure my Java application takes reasonable steps to be robust, and part of that involves shutting down gracefully. I am reading about shutdown hooks and I don't actually get how to make use of them in practice.

Is there a practical example out there?

Let's say I had a really simple application like this one below, which writes numbers to a file, 10 to a line, in batches of 100, and I want to make sure a given batch finishes if the program is interrupted. I get how to register a shutdown hook but I have no idea how to integrate that into my application. Any suggestions?

package com.example.test.concurrency;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class GracefulShutdownTest1 {
    final private int N;
    final private File f;
    public GracefulShutdownTest1(File f, int N) { this.f=f; this.N = N; }

    public void run()
    {
        PrintWriter pw = null;
        try {
            FileOutputStream fos = new FileOutputStream(this.f);
            pw = new PrintWriter(fos);
            for (int i = 0; i < N; ++i)
                writeBatch(pw, i);
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally
        {
            pw.close();
        }       
    }

    private void writeBatch(PrintWriter pw, int i) {
        for (int j = 0; j < 100; ++j)
        {
            int k = i*100+j;
            pw.write(Integer.toString(k));
            if ((j+1)%10 == 0)
                pw.write('\n');
            else
                pw.write(' ');
        }
    }

    static public void main(String[] args)
    {
        if (args.length < 2)
        {
            System.out.println("args = [file] [N] "
                    +"where file = output filename, N=batch count");
        }
        else
        {
            new GracefulShutdownTest1(
                    new File(args[0]), 
                    Integer.parseInt(args[1])
            ).run();
        }
    }
}
aioobe
  • 413,195
  • 112
  • 811
  • 826
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • See also https://stackoverflow.com/questions/8722826/when-do-i-need-to-call-this-method-runtime-getruntime-addshutdownhook – flow2k Aug 28 '18 at 18:49

1 Answers1

170

You could do the following:

  • Let the shutdown hook set some AtomicBoolean (or volatile boolean) "keepRunning" to false
  • (Optionally, .interrupt the working threads if they wait for data in some blocking call)
  • Wait for the working threads (executing writeBatch in your case) to finish, by calling the Thread.join() method on the working threads.
  • Terminate the program

Some sketchy code:

  • Add a static volatile boolean keepRunning = true;
  • In run() you change to

    for (int i = 0; i < N && keepRunning; ++i)
        writeBatch(pw, i);
    
  • In main() you add:

    final Thread mainThread = Thread.currentThread();
    Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
            keepRunning = false;
            mainThread.join();
        }
    });
    

That's roughly how I do a graceful "reject all clients upon hitting Control-C" in terminal.


From the docs:

When the virtual machine begins its shutdown sequence it will start all registered shutdown hooks in some unspecified order and let them run concurrently. When all the hooks have finished it will then run all uninvoked finalizers if finalization-on-exit has been enabled. Finally, the virtual machine will halt.

That is, a shutdown hook keeps the JVM running until the hook has terminated (returned from the run()-method.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 14
    Ah -- so if I understand right, the gist of what you're saying is that the shutdown hook has an opportunity to communicate with other currently-running threads, and to hold off the VM shutdown (e.g. by using Thread.join()) until it is satisfied that a quick cleanup has been completed. That's the point I was missing. thanks! – Jason S May 27 '10 at 14:50
  • 2
    Yes. You got it. Updated the answer with a few sentences from the docs. – aioobe May 27 '10 at 14:57
  • 1
    Sorry in advance,but shouldnt GracefulShutdownTest1 implement Runnable? – Victor Jul 18 '13 at 20:00
  • Will this also work a GUI was started in the main thread? I know it's not the best way to do it (use EDT instead), but I'm working on old code. – Agostino Mar 10 '14 at 09:54
  • Shutdown hooks are unrelated to GUI code. The two should work together just fine. – aioobe Mar 10 '14 at 09:56
  • @aioobe, Are there any limitations to how long the shutdown hook can run? For example, can it "prevent" the virtual machine from stopping *indefinitely*? – Pacerier Jul 18 '14 at 12:45
  • I don't think there is any limitations. You can prevent the JVM from shutting down by spawning a non daemon thread as well. – aioobe Jul 19 '14 at 10:14
  • So if this example had been using multiple threads, your `join()` call would be made on the thread doing the work? – Duncan Jones Aug 21 '14 at 07:13
  • 1
    @MartynasJusevičius, the JVM terminates when all non-daemon threads have terminated. If the `mainThread` is a daemon thread, then the `join` guarantees that we wait for it to finish before letting the JVM shut down. – aioobe Jun 20 '19 at 14:56