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

时间:2011-08-12 05:54:58

标签: java runtime executable-jar

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

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

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

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

11 个答案:

答案 0 :(得分:12)

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

绑定ServerSocket。如果它无法绑定,则中止启动。由于ServerSocket只能绑定一次,因此只能运行程序的单个实例。

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

答案 1 :(得分:7)

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

  1. 使用Java shutdown hook
  2. 让您的锁定文件保存主进程号。当您浏览另一个实例时,该过程应该存在。如果在您的系统中找不到它,您可以认为锁可以被解除并被覆盖。

答案 2 :(得分:4)

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

答案 3 :(得分:4)

您可以使用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);
}

还存活垃圾。 一旦你的过程结束就会释放锁定,无论是经常退出还是崩溃都无关紧要。

答案 4 :(得分:3)

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

以下是检测到抛出异常的示例:

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

答案 5 :(得分:2)

您可以创建像

这样的服务器套接字
       new ServerSocket(65535, 1, InetAddress.getLocalHost());

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

答案 6 :(得分:1)

  

..我可以使用其他机制吗?

如果应用程序。有一个GUI,它可以使用Java Web Start启动。提供给web-start的JNLP API提供SingleInstanceService。这是我的demo. of the SingleInstanceService

答案 7 :(得分:1)

你可以写这样的东西。

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

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

这对我有用

答案 8 :(得分:0)

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

有关详情,请查看https://docs.oracle.com/javase/7/docs/api/java/io/File.html

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

答案 9 :(得分:0)

一段时间以来,我一直在同一个问题上挣扎……这里提出的想法对我都不起作用。在所有情况下,锁(文件,套接字或其他方式)都不会持久到第二个流程实例中,因此第二个实例仍然可以运行。

因此,我决定尝试一种老式的方法,简单地使用第一个进程的进程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
}

希望您对此有帮助。

答案 10 :(得分:0)

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

<块引用>

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

这是如何从documentation

使用它的示例
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
    }
}