简体   繁体   中英

(Java) exiting a loop “remotely”

I have a piece of Java program that essentially does the following:

public static void main(String[] args)
{
  while(true)
  {
  // does stuff ...
  }
}

The infinite loop is there by design - when left alone the program will loop infinitely. For the most part it works fine. However, sometimes I want to take the program down for maintenance, and when I take it down I want to make sure that it runs through all the code in the loop to the end then exit.

I am wondering what is the best solution for this. One idea I have in mind is to do something like this:

public static void main(String[] args)
{
    File f = new File("C:\exit.txt");
    while(!f.exists())
    {
        // does stuff ...
    }
}

which basically allows me to gracefully get out of the loop by creating a file called "exit.txt". This is probably OK for my purposes, but I would like to know if there are better, alternative methods.

You could make use of runtime shutdown hook. That way you won't need to use console input in order to stop the loop. If JVM is being closed normally then shutdown hook thread will run. This thread will wait for the end of current loop iteration. Keep in mind that there are some limitations when using hooks though: https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#addShutdownHook-java.lang.Thread-

import java.util.concurrent.CountDownLatch;

public class Test {

    private volatile static CountDownLatch lastIterationLatch = null;
    private static boolean stop = false;

    public static void main(String [] args) throws Exception {

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
               lastIterationLatch = new CountDownLatch(1);
               try {
                   lastIterationLatch.await();
               } catch (Exception e) {
                   throw new RuntimeException(e);
               }
            }
        });

        while(!stop) {
           System.out.println("iteration start");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("iteration end");
           if(lastIterationLatch != null) {
               stop = true;
               lastIterationLatch.countDown();
           }
        }
    }
}

I think that the WatchService that was introduced in Java 7 may be of use here (if you prefer a file based approach that is). From the JavaDocs :

A watch service that watches registered objects for changes and events. For example a file manager may use a watch service to monitor a directory for changes so that it can update its display of the list of files when files are created or deleted.

Basically what this means is that you can set up a WatchService that can watch a folder for changes. When a change occurs you can choose what actions to take.

The following code uses the WatchService to monitor a specified folder for changes. When a change has happened it executes a Runnable that the caller has provided (the method runWhenItIsTimeToExit ).

public class ExitChecker {
    private final Path dir;
    private final Executor executor;
    private final WatchService watcher;

    // Create the checker using the provided path but with some defaults for
    // executor and watch service
    public ExitChecker(final Path dir) throws IOException {
        this(dir, FileSystems.getDefault().newWatchService(), Executors.newFixedThreadPool(1));
    }

    // Create the checker using the provided path, watcher and executor
    public ExitChecker(final Path dir, final WatchService watcher, final Executor executor) {
        this.dir = dir;
        this.watcher = watcher;
        this.executor = executor;
    }

    // Wait for the folder to be modified, then invoke the provided runnable
    public void runWhenItIsTimeToExit(final Runnable action) throws IOException {
        // Listen on events in the provided folder
        dir.register(watcher,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        // Run it async, otherwise the caller thread will be blocked
        CompletableFuture.runAsync(() -> {
            try {
                watcher.take();
            } catch (InterruptedException e) {
                // Ok, we got interrupted
            }
        }, executor).thenRunAsync(action);
    }
}

So, how do we use the checker then? Well, the following code illustrates this:

public static void main(String... args) throws IOException, InterruptedException {
    // Setup dirs in the home folder
    final Path directory = Files.createDirectories(
            new File(System.getProperty("user.home") + "/.exittst").toPath());

    // In this case we use an AtomicBoolean to hold the "exit-status"
    AtomicBoolean shouldExit = new AtomicBoolean(false);

    // Start the exit checker, provide a Runnable that will be executed
    // when it is time to exit the program
    new ExitChecker(directory).runWhenItIsTimeToExit(() -> {
        // This is where your exit code will end up. In this case we
        // simply change the value of the AtomicBoolean
        shouldExit.set(true);
    });

    // Start processing
    while (!shouldExit.get()) {
        System.out.println("Do something in loop");
        Thread.sleep(1000);
    }

    System.out.println("Exiting");
}

Finally, how do you exit the program then? Well simply touch a file in the specified folder. Example:

cd ~/.exittst
touch exit-now.please

Resources:

One could employ some sophisticated techniques here. The file watchdog is one option. RMI could be another. But in fact, the mechanisms that are required here are quite simple, so I'd like to propose another (very simple) solution.

Note: This solution is just one option, showing that it is possible to do it that way. It is not a general recommendation, and whether it is "good" or not depends on the application case.

The solution is simply based on Sockets . The ServerSocket#accept method already encapsulates the functionality that you want:

Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.

Based on this, it is trivial to create such a "remote control": The server just waits for a connection, and sets a flag when the connection is opened:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;

class RemoteExitServer
{
    private final AtomicBoolean flag = new AtomicBoolean();

    RemoteExitServer()
    {
        Thread t = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                waitForConnection();
            }
        });
        t.setDaemon(true);
        t.start();
    }

    private void waitForConnection()
    {
        ServerSocket server = null;
        Socket socket = null;
        try
        {
            server = new ServerSocket(1234);
            socket = server.accept();
            flag.set(true);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (server != null)
            {
                try
                {
                    server.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
            if (socket != null)
            {
                try
                {
                    socket.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }

    }

    boolean shouldExit()
    {
        return flag.get();
    }
}

The client does exactly that: It opens a connection, and nothing else

import java.io.IOException;
import java.net.Socket;

public class RemoteExitClient
{
    public static void main(String[] args)
    {
        Socket socket = null;
        try
        {
            socket = new Socket("localhost", 1234);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (socket != null)
            {
                try
                {
                    socket.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}

The application is then also very simple:

public class RemoteExitTest
{
    public static void main(String[] args)
    {
        RemoteExitServer e = new RemoteExitServer();

        while (!e.shouldExit())
        {
            System.out.println("Working...");
            try
            {
                Thread.sleep(1000);
            }
            catch (InterruptedException e1)
            {
                e1.printStackTrace();
            }
        }
        System.out.println("done");
    }
}

(The code could be made even more concise with try-with-resources , but this should not matter here)

For something quick/dirty, use Signals:

boolean done = false;

// ...

Signal.handle(new Signal("USR1"), new SignalHandler() {
    @Override
    public void handle(Signal signal) {
        // signal triggered ...
        done = true;
    }
});

// ...

while(!done) { ... }

Then, use kill -USR1 _pid_ to trigger the signal.

You could use a AtomicBoolean as in the test program below. To suspend just type true into the console to resume type false. The program will never exit.

public class Test2 {
public static void main(String[] args) {
    final AtomicBoolean suspended = new AtomicBoolean(false);

    new Thread() {
        public void run() {
            while (true)
            {
                Scanner sc = new Scanner(System.in);
                boolean b = sc.nextBoolean();
                suspended.set(b);
            }
        }
    }.start();


    while(true){
        if(!suspended.get()){
            System.out.println("working");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else{
           //System.exit(0) //if you want to exit rather than suspend uncomment.
        }
    }

}

}

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