【问题标题】:(Java) exiting a loop "remotely"(Java)“远程”退出循环
【发布时间】:2015-02-28 08:39:10
【问题描述】:

我有一个 Java 程序,它基本上执行以下操作:

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

无限循环是设计出来的——如果不理会,程序将无限循环。在大多数情况下,它工作正常。但是,有时我想把程序下线进行维护,当我下线时,我想确保它运行完循环中的所有代码,然后退出。

我想知道什么是最好的解决方案。我想到的一个想法是做这样的事情:

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

这基本上允许我通过创建一个名为“exit.txt”的文件来优雅地退出循环。这对于我的目的来说可能没问题,但我想知道是否有更好的替代方法。

【问题讨论】:

  • 对于简单的事情,您可以使用downForMaintaince 之类的布尔值,并根据需要将其设为'truefalse。然后while(!downForMaintance) { //infinite loop }
  • 如何从外部操作这个 downForMaintaince 变量?
  • @AnirbanNag,他的意思是没有内部“帮助”,所以外部程序可以发出这个过程的信号。这些答案可以指导你stackoverflow.com/questions/3244755/…
  • 这个程序是否在终端中持续运行?如果是这样,您可以让程序等到您输入exitSystem.in
  • @JarrodRoberson 我不会认为这是重复的。这里的关键是这应该从程序外部“远程”完成,而不是基于程序内部的某些用户输入。

标签: java infinite-loop


【解决方案1】:

您可以使用运行时关闭挂钩。这样您就不需要使用控制台输入来停止循环。如果 JVM 正常关闭,则关闭钩子线程将运行。该线程将等待当前循环迭代的结束。请记住,使用钩子时有一些限制: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();
           }
        }
    }
}

【讨论】:

  • 同意你可以这样做。当我给出控制台答案时,我主要指向一个模式,控制台只是一个可能的信号。它还允许程序在不退出的情况下暂停和恢复。
【解决方案2】:

这里可以使用一些复杂的技术。文件看门狗是一种选择。 RMI 可能是另一个。但实际上,这里需要的机制非常简单,所以我想提出另一个(非常简单)的解决方案。

注意:此解决方案只是一种选择,表明这样做是可能。这不是一般性的推荐,是否“好”取决于应用案例。

解决方案只是基于 SocketsServerSocket#accept 方法已经封装了你想要的功能:

侦听要与此套接字建立的连接并接受它。该方法阻塞,直到建立连接。

基于此,创建这样一个“远程控制”是微不足道的:服务器只是等待连接,并在连接打开时设置一个标志:

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

客户端正是这样做的:它打开一个连接,没有别的

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

那么应用程序也很简单:

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

(使用 try-with-resources 可以使代码更加简洁,但这在这里应该无关紧要)

【讨论】:

  • 到目前为止,似乎每个答案都至少被否决了一次。我真的很想看到投票者不认为“没有帮助”的答案。从系统的角度来看,与正在运行的 Java 应用程序通信最多有三种选择:1. 通过文件,2. 通过网络,3. 可能以某种方式使用 JNI 魔法。后者似乎不切实际。基于文件的方法对我来说有点笨拙。所以剩下的是基于网络的方法(无论是通过 RMI 还是自己的套接字)。这与 “远程控制” 非常接近。
【解决方案3】:

我认为 Java 7 中引入的 WatchService 可能在这里有用(如果您更喜欢基于文件的方法)。来自JavaDocs

监视注册对象的更改和事件的监视服务。例如,文件管理器可以使用监视服务来监视目录的更改,以便在创建或删除文件时更新其文件列表的显示。

这基本上意味着您可以设置一个WatchService 来监视文件夹的更改。当发生变化时,您可以选择要采取的行动。

以下代码使用WatchService 监视指定文件夹的更改。当发生更改时,它会执行调用者提供的Runnable(方法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);
    }
}

那么,我们如何使用检查器呢?好吧,下面的代码说明了这一点:

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

最后,你如何退出程序呢?那么只需触摸指定文件夹中的文件即可。示例:

cd ~/.exittst
touch exit-now.please

资源:

【讨论】:

    【解决方案4】:

    您可以在下面的测试程序中使用 AtomicBoolean。 要暂停,只需在控制台中输入 true 以恢复输入 false。程序永远不会退出。

    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.
            }
        }
    
    }
    

    }

    【讨论】:

    • 如何让程序停止使用此设置?
    • 你真的想做 System.exit 吗?如果是这样,请在 System.exit(0) 中添加 else 子句。我已经编辑了代码以反映您需要的更改。
    【解决方案5】:

    对于快速/肮脏的东西,使用信号:

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

    然后,使用kill -USR1 _pid_ 触发信号。

    【讨论】:

    猜你喜欢
    • 2012-09-14
    • 1970-01-01
    • 1970-01-01
    • 2023-02-23
    • 1970-01-01
    • 1970-01-01
    • 2016-02-27
    • 2015-01-18
    • 2015-08-07
    相关资源
    最近更新 更多