防止启动java应用程序的多个实例

防止启动java应用程序的多个实例,java,runtime,executable-jar,Java,Runtime,Executable Jar,我想防止用户多次并行运行我的java应用程序 为了防止这种情况,我在打开应用程序时创建了一个锁文件,在关闭应用程序时删除了锁文件 当应用程序运行时,您不能打开另一个jar实例。但是,如果通过任务管理器终止应用程序,则不会触发应用程序中的窗口关闭事件,也不会删除锁定文件 如何确保锁文件方法有效,或者我可以使用什么其他机制?类似的讨论在 绑定服务器套接字。如果绑定失败,则中止启动。由于ServerSocket只能绑定一次,因此程序只能运行单个指令 在你问之前,不。仅仅因为你绑定了一个ServerS

我想防止用户多次并行运行我的java应用程序

为了防止这种情况,我在打开应用程序时创建了一个锁文件,在关闭应用程序时删除了锁文件

当应用程序运行时,您不能打开另一个jar实例。但是,如果通过任务管理器终止应用程序,则不会触发应用程序中的窗口关闭事件,也不会删除锁定文件

如何确保锁文件方法有效,或者我可以使用什么其他机制?

类似的讨论在

绑定服务器套接字。如果绑定失败,则中止启动。由于ServerSocket只能绑定一次,因此程序只能运行单个指令


在你问之前,不。仅仅因为你绑定了一个ServerSocket,并不意味着你对网络流量开放。只有当程序使用accept()开始“侦听”端口时,该操作才会生效。

您可以将创建锁文件的进程的进程id写入该文件。
当您遇到一个现有的锁文件时,您不仅要退出,还要检查具有该id的进程是否仍然处于活动状态。如果没有,您可以继续。

我看到两个选项您可以尝试:

  • 使用Java
  • 让锁文件保存主进程号。当您访问另一个实例时,该进程应该存在。如果在您的系统中找不到锁,您可以假定可以解除并覆盖锁
  • …我还可以使用什么其他机制


    如果应用程序。有一个GUI,可以使用它启动。提供给web start的API提供了
    SingleInstanceService
    。这是我的。

    您可以创建一个服务器套接字,如

           new ServerSocket(65535, 1, InetAddress.getLocalHost());
    

    在代码的最开始部分。然后,如果在主块中发现AddressalReadyUse异常,您可以显示相应的消息。

    您可以这样编写

    如果文件存在,请尝试删除它。如果它不能删除。我们可以说应用程序已经在运行了

    现在再次创建相同的文件并重定向sysout和syserr


    这适用于我

    您可以使用文件锁,这也适用于多个用户共享端口的环境:

    String userHome = System.getProperty("user.home");
    File file = new File(userHome, "my.lock");
    try {
        FileChannel fc = FileChannel.open(file.toPath(),
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE);
        FileLock lock = fc.tryLock();
        if (lock == null) {
            System.out.println("another instance is running");
        }
    } catch (IOException e) {
        throw new Error(e);
    }
    
    也可以在垃圾收集中生存。
    一旦进程结束,锁就会被释放,无论是常规退出还是崩溃或其他什么情况。

    文件类中已经有可用的java方法来实现同样的功能。方法是deleteOnExit(),它确保在JVM退出时自动删除文件。但是,它不适用于强制终止。在强制终止的情况下,应使用FileLock

    有关更多详细信息,请查看:

    因此,可以在main方法中使用的代码片段如下所示:

    public static void main(String args[]) throws Exception {
    
        File f = new File("checkFile");
    
        if (!f.exists()) {
            f.createNewFile();
        } else {
            System.out.println("App already running" );
            return;
        }
    
        f.deleteOnExit();
    
        // whatever your app is supposed to do
        System.out.println("Blah Blah")
    }
    

    创建服务器套接字,在应用程序启动时绑定到具有实例的特定端口是一条捷径。
    请注意,
    ServerSocket.accept()
    会阻塞,因此在它自己的线程中运行它时,不阻塞主
    线程是有意义的

    以下是一个检测到异常时引发异常的示例:

    public static void main(String[] args) {       
        assertNoOtherInstanceRunning();
        ...     // application code then        
    }
    
    public static void assertNoOtherInstanceRunning() {       
        new Thread(() -> {
            try {
                new ServerSocket(9000).accept();
            } catch (IOException e) {
              throw new RuntimeException("the application is probably already started", e);
            }
        }).start();       
    }
    

    我和这个问题斗争了一段时间。。。这里提出的想法都不适合我。在所有情况下,锁(文件、套接字或其他)都不会持久存在于第二个进程实例中,因此第二个实例仍在运行

    所以我决定尝试一种老派的方法,简单地用第一个进程的进程id包装一个.pid文件。然后,如果找到.pid文件,任何第二个进程都将退出,并且文件中指定的进程号也被确认仍在运行。这种方法对我有效

    这里有相当多的代码,我在这里提供了完整的代码供您使用。。。一个完整的解决方案

    package common.environment;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    import javax.annotation.Nonnull;
    import javax.annotation.Nullable;
    import java.io.*;
    import java.nio.charset.Charset;
    
    public class SingleAppInstance
    {
        private static final @Nonnull Logger log = LogManager.getLogger(SingleAppInstance.class.getName());
    
        /**
         * Enforces that only a single instance of the given component is running. This
         * is resilient to crashes, unexpected reboots and other forceful termination
         * scenarios.
         *
         * @param componentName = Name of this component, for disambiguation with other
         *   components that may run simultaneously with this one.
         * @return = true if the program is the only instance and is allowed to run.
         */
        public static boolean isOnlyInstanceOf(@Nonnull String componentName)
        {
            boolean result = false;
    
            // Make sure the directory exists
            String dirPath = getHomePath();
            try
            {
                FileUtil.createDirectories(dirPath);
            }
            catch (IOException e)
            {
                throw new RuntimeException(String.format("Unable to create directory: [%s]", dirPath));
            }
    
            File pidFile = new File(dirPath, componentName + ".pid");
    
            // Try to read a prior, existing pid from the pid file. Returns null if the file doesn't exist.
            String oldPid = FileUtil.readFile(pidFile);
    
            // See if such a process is running.
            if (oldPid != null && ProcessChecker.isStillAllive(oldPid))
            {
                log.error(String.format("An instance of %s is already running", componentName));
            }
            // If that process isn't running, create a new lock file for the current process.
            else
            {
                // Write current pid to the file.
                long thisPid = ProcessHandle.current().pid();
                FileUtil.createFile(pidFile.getAbsolutePath(), String.valueOf(thisPid));
    
                // Try to be tidy. Note: This won't happen on exit if forcibly terminated, so we don't depend on it.
                pidFile.deleteOnExit();
    
                result = true;
            }
    
            return result;
        }
    
        public static @Nonnull String getHomePath()
        {
            // Returns a path like C:/Users/Person/
            return System.getProperty("user.home") + "/";
        }
    }
    
    class ProcessChecker
    {
        private static final @Nonnull Logger log = LogManager.getLogger(io.cpucoin.core.platform.ProcessChecker.class.getName());
    
        static boolean isStillAllive(@Nonnull String pidStr)
        {
            String OS = System.getProperty("os.name").toLowerCase();
            String command;
            if (OS.contains("win"))
            {
                log.debug("Check alive Windows mode. Pid: [{}]", pidStr);
                command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\"";
            }
            else if (OS.contains("nix") || OS.contains("nux"))
            {
                log.debug("Check alive Linux/Unix mode. Pid: [{}]", pidStr);
                command = "ps -p " + pidStr;
            }
            else
            {
                log.warn("Unsupported OS: Check alive for Pid: [{}] return false", pidStr);
                return false;
            }
            return isProcessIdRunning(pidStr, command); // call generic implementation
        }
    
        private static boolean isProcessIdRunning(@Nonnull String pid, @Nonnull String command)
        {
            log.debug("Command [{}]", command);
            try
            {
                Runtime rt = Runtime.getRuntime();
                Process pr = rt.exec(command);
    
                InputStreamReader isReader = new InputStreamReader(pr.getInputStream());
                BufferedReader bReader = new BufferedReader(isReader);
                String strLine;
                while ((strLine = bReader.readLine()) != null)
                {
                    if (strLine.contains(" " + pid + " "))
                    {
                        return true;
                    }
                }
    
                return false;
            }
            catch (Exception ex)
            {
                log.warn("Got exception using system command [{}].", command, ex);
                return true;
            }
        }
    }
    
    class FileUtil
    {
        static void createDirectories(@Nonnull String dirPath) throws IOException
        {
            File dir = new File(dirPath);
            if (dir.mkdirs())   /* If false, directories already exist so nothing to do. */
            {
                if (!dir.exists())
                {
                    throw new IOException(String.format("Failed to create directory (access permissions problem?): [%s]", dirPath));
                }
            }
        }
    
        static void createFile(@Nonnull String fullPathToFile, @Nonnull String contents)
        {
            try (PrintWriter writer = new PrintWriter(fullPathToFile, Charset.defaultCharset()))
            {
                writer.print(contents);
            }
            catch (IOException e)
            {
                throw new RuntimeException(String.format("Unable to create file at %s! %s", fullPathToFile, e.getMessage()), e);
            }
        }
    
        static @Nullable String readFile(@Nonnull File file)
        {
            try
            {
                try (BufferedReader fileReader = new BufferedReader(new FileReader(file)))
                {
                    StringBuilder result = new StringBuilder();
    
                    String line;
                    while ((line = fileReader.readLine()) != null)
                    {
                        result.append(line);
                        if (fileReader.ready())
                            result.append("\n");
                    }
                    return result.toString();
                }
            }
            catch (IOException e)
            {
                return null;
            }
        }
    }
    
    要使用它,只需像这样调用它:

    if (!SingleAppInstance.isOnlyInstanceOf("my-component"))
    {
        // quit
    }
    

    我希望这对您有所帮助。

    最后我找到了一个非常简单的库来实现这一点。你可以用它

    JUnique库可用于防止用户同时运行 为同一Java应用程序的多个实例计时

    这是一个如何使用它的示例


    好主意+1.但是这个想法,保留一个文件来检查应用程序是否正在运行,感觉有点难看。不是吗。难道没有任何优雅的方法吗???我不认为这是其他流行应用正在使用的方法。我很好奇。你也应该检查进程是否正确(想象一下崩溃,重新启动机器。但是,我们无论如何都可以遍历所有进程,并且可以摆脱难看的文件检查。(我自己使用套接字,它允许转发参数)。至少在Windows上,这不是一个好主意,因为进程ID可以循环使用。因此,您可能会得到误报。如果其他应用程序使用65535发送数据,即使没有实例运行,您也不会绑定到此端口。PSA:服务器套接字工作得很好,只是别忘了将其分配给不会收到垃圾的对象。c在整个流程的生命周期内被收集或排除的好处将是短暂的(或者更糟的是,它的工作时间将足够长,让你想知道它停止时发生了什么)。如果有多个用户尝试运行该应用程序,会发生什么情况?它们在打开套接字时不会发生冲突吗?除非windows taskmanager将其杀死或Linux kill-9,否则此设计有一个较小但真实的缺点:如果该应用程序意外退出,pid文件将不会被删除。在后续运行中,其他人可能会r正在运行的进程可能恰好与pid文件中的pid相同,并且程序将不会运行。您必须先删除pid文件。请注意,这会留下锁文件。如果您希望在退出时删除它,可以调用file.deleteOnExit()如果进程正常退出,这将起作用。在我的例子中,当我在方法中声明锁时,锁被垃圾收集。然后我将它移动到一个实例变量,现在它就像一个符咒一样工作。我使用FileLock和FileChannel instanc
    public static void main(String[] args) {
        String appId = "myapplicationid";
        boolean alreadyRunning;
        try {
            JUnique.acquireLock(appId);
            alreadyRunning = false;
        } catch (AlreadyLockedException e) {
            alreadyRunning = true;
        }
        if (!alreadyRunning) {
            // Start sequence here
        }
    }