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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7036108/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:24:07  来源:igfitidea点击:

Prevent launching multiple instances of a java application

javaruntimeexecutable-jar

提问by Sathish

I want to prevent the user from running my java application multiple times in parallel.

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

To prevent this, I have created a lock file when am opening the application, and delete the lock file when closing the application.

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

When the application is running, you can not open an another instance of jar. However, if you kill the application through task manager, the window closing event in the application is not triggered and the lock file is not deleted.

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

How can I make sure the lock file method works or what other mechanism could I use?

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

回答by Sap

Similar discussion is at http://www.daniweb.com/software-development/java/threads/83331

类似的讨论在 http://www.daniweb.com/software-development/java/threads/83331

Bind a ServerSocket. If it fails to bind then abort the startup. Since a ServerSocket can be bound only once, only single instsances of the program will be able to run.

绑定一个 ServerSocket。如果绑定失败,则中止启动。由于一个 ServerSocket 只能绑定一次,所以只有程序的单个实例才能运行。

And before you ask, no. Just because you bind a ServerSocket, does not mean you are open to network traffic. That only comes into effect once the program starts "listening" to the port with accept().

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

回答by sgibly

I see two options you can try:

我看到您可以尝试两种选择:

  1. Use a Java shutdown hook
  2. Have your lock file hold the main process number. The process should exist when you lanuch another instance. If it's not found in your system, you can assume that the lock can be dismissed and overwritten.
  1. 使用 Java关闭挂钩
  2. 让你的锁文件保存主进程号。当您启动另一个实例时,该进程应该存在。如果在您的系统中找不到它,您可以假设可以解除和覆盖锁定。

回答by Manuel

You could use a FileLock, this also works in environments where multiple users share ports:

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

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

Also survives Garbage Collection. The lock is released once your process ends, doesn't matter if regular exit or crash or whatever.

也幸免于垃圾收集。一旦您的进程结束,锁就会被释放,无论是正常退出还是崩溃或其他什么。

回答by Thilo

You could write the process id of the process that created the lock file into the file. When you encounter an existing lock file, you do not just quit, but you check if the process with that id is still alive. If not, you can go ahead.

您可以将创建锁定文件的进程的进程 ID 写入文件。当您遇到现有的锁定文件时,您不仅要退出,还要检查具有该 ID 的进程是否还活着。如果没有,你可以继续。

回答by davidxxx

Creating a server socket, bounds to a specific port with a ServerSocketinstance as the application starts is a straight way.
Note that ServerSocket.accept()blocks, so running it in its own thread makes sense to not block the main Thread.

创建服务器套接字,在应用程序启动时使用ServerSocket实例绑定到特定端口是一种直接的方法。
请注意ServerSocket.accept()阻塞,因此在其自己的线程中运行它是有意义的,不要阻塞 main Thread

Here is an example with a exception thrown as detected :

这是一个检测到抛出异常的示例:

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

回答by Sudheep Vallipoyil

You can create a Server socket like

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

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

at very beginning of your code. Then if AddressAlreadyInUse exception caught in main block you can display the appropriate message.

在你的代码的最开始。然后,如果在主块中捕获到 AddressAlreadyInUse 异常,您可以显示相应的消息。

回答by Karthej Reddy

You can write something like this.

你可以写这样的东西。

If file exists try to delete it. if it is not able to delete. We can say that application is already running.

如果文件存在尝试删除它。如果无法删除。我们可以说应用程序已经在运行。

Now create the same file again and redirect the sysout and syserr.

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

This works for me

这对我有用

回答by Andrew Thompson

..what other mechanism could I use?

..我可以使用什么其他机制?

If the app. has a GUI it can be launched using Java Web Start. The JNLPAPI provided to web-start offers the SingleInstanceService. Here is my demo. of the SingleInstanceService.

如果应用程序。有一个可以使用Java Web Start 启动的 GUI 。提供给 web-start的JNLPAPI 提供了SingleInstanceService. 这是我的演示。的SingleInstanceService

回答by akshaya pandey

There are already available java methods in File class to achieve the same. The method is deleteOnExit() which ensure the file is automatically deleted when the JVM exits. However, it does not cater to forcible terminations. One should use FileLock in case of forcible termination.

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

For more details check, https://docs.oracle.com/javase/7/docs/api/java/io/File.html

有关更多详细信息,查看https://docs.oracle.com/javase/7/docs/api/java/io/File.html

Thus code snippet which could be used in the main method can be like :

因此,可以在 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")
}

回答by djenning90

I struggled with this same problem for a while... none of the ideas presented here worked for me. In all cases, the lock (file, socket or otherwise) did not persist into the 2nd process instance, so the 2nd instance still ran.

我在同样的问题上挣扎了一段时间……这里提出的想法都不适合我。在所有情况下,锁(文件、套接字或其他)都没有持续到第二个进程实例中,所以第二个实例仍然运行。

So I decided to try an old school approach to simply crate a .pid file with the process id of the first process. Then any 2nd process would quit if it finds the .pid file, and also the process number specified in the file is confirmed to be still running. This approach worked for me.

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

There is a fair bit of code, which I provide here in full for your use... a complete solution.

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

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

To use it, simply invoke it like this:

要使用它,只需像这样调用它:

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

I hope you find this helpful.

我希望你觉得这有帮助。