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

32

我希望防止用户同时运行我的Java应用程序。

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

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

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

13个回答

15

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

也能在垃圾回收时存活。 一旦您的进程结束,无论是正常退出、崩溃还是其他情况,锁都会被释放。


请注意,这会留下锁定文件。如果您想在退出时将其删除,可以调用file.deleteOnExit(),如果进程正常退出,则该调用可行。 - KC Baltz
1
在我的情况下,当我将锁声明在一个方法中时,它被垃圾收集了。然后我将它移到实例变量中,现在它就像魔法一样工作。 - kske
我在方法内部仅使用FileLock和FileChannel实例作为本地变量,刚刚尝试了一下使用jvisualvm触发的GC,第二次启动我的应用程序仍然看到了第一次启动的锁定。@CyB3RC0nN0R,您能否详细说明并展示您的代码? - Manuel

13

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

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

在你问之前,不是所有绑定了ServerSocket的程序都可以接受网络流量。只有当程序使用accept()开始“监听”端口时,才会生效。


3
注意:服务器套接字很好用,只要别忘了将其分配给不会在进程生存期内被垃圾回收的对象,否则排除效果将是短暂的(或更糟糕的是,它可能会工作足够长的时间,让你想知道它停止工作的原因)。 - Nathan Williams
1
如果有多个用户尝试运行应用程序会发生什么?他们不会在打开套接字时发生冲突吗? - pupeno

6
我看到你有两个选择:
  1. 使用Java的关闭挂钩(shutdown hook)
  2. 让你的锁文件保存主进程号。当你启动另一个实例时,该进程应该存在。如果在你的系统中找不到该进程,则可以认为该锁可以被忽略和覆盖。

1
除非Windows任务管理器杀死它或Linux kill-9,否则你就完了。 - JPM

5
创建一个服务器套接字,使用应用程序启动时的 ServerSocket 实例绑定到特定端口是一种简单的方法。
请注意,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();       
}

3

您可以将创建锁定文件的进程的进程ID写入该文件。

当您遇到现有的锁定文件时,不要仅仅退出,而是检查具有该ID的进程是否仍然存在。 如果不存在,则可以继续进行。


1
好主意!+1。但是这个想法,保持一个文件来检查应用程序是否正在运行,感觉有点丑陋。不是吗?难道就没有更优雅的方法吗?我不认为其他流行应用程序使用的是这种方法。我很好奇... - Anubis
你还应该检查进程是否正确(想象一下崩溃,重新启动机器。但是,我们可以无论如何都遍历所有进程,并且可以摆脱丑陋的文件检查。(我自己使用套接字,这允许转发参数)。 - Eiko
至少在Windows上,这不是一个好主意,因为进程ID可以被回收利用。因此,你可能会得到一个错误的结果。请参见https://dev59.com/dV8d5IYBdhLWcg3w6luM。 - CleanUp

1
已经有File类中可用的Java方法来实现相同的功能。该方法是deleteOnExit(),它确保在JVM退出时自动删除文件。但是,它不适用于强制终止。在强制终止的情况下,应使用FileLock。
更多详情请查看https://docs.oracle.com/javase/7/docs/api/java/io/File.html 因此,在主方法中可以使用以下代码片段:
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")
}

请看一下我的回答:https://dev59.com/WGw05IYBdhLWcg3wxkqr#74257460 我使用了你的答案并稍微改进了一下。我也给了你们信用。 - Remzi Cavdar

1
您可以创建一个服务器套接字,例如:
       new ServerSocket(65535, 1, InetAddress.getLocalHost());

在你的代码开头加入try-except块。然后,在主块中捕获AddressAlreadyInUse异常,你可以显示相应的信息。

1
如果另一个应用程序使用65535发送数据,则即使没有实例正在运行,您也无法绑定到此端口。 - ayanamist

0

简单锁和高级锁

我为这个问题开发了两种解决方案。我还在寻找一种不使用任何库和大量代码的简单方法。

我的解决方案基于:https://dev59.com/WGw05IYBdhLWcg3wxkqr#46705579,我对其进行了改进。因此,我要感谢@akshaya pandey@rbento

简单文件锁

package YOUR_PACKAGE_NAME;

import java.io.File;
import java.io.IOException;

/**
 * Minimal reproducible example (MRE) - Example of a simple lock file.
 * @author Remzi Cavdar - ict@remzi.info - <a href="https://github.com/Remzi1993">@Remzi1993</a>
 */
public class Main {
    public static void main(String[] args) {
        /*
         * Prevents the user of starting multiple instances of the application.
         * This is done by creating a temporary file in the app directory.
         * The temp file should be excluded from git and is called App.lock in this example.
         */
        final File FILE = new File("App.lock");

        try {
            if (FILE.createNewFile()) {
                System.out.println("Starting application");
            } else {
                System.err.println("The application is already running!");
                return;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        /*
         * Register a shutdown hook to delete the lock file when the application is closed. Even when forcefully closed
         * with the task manager. (Tested on Windows 11 with JavaFX 19)
         */
        FILE.deleteOnExit();
        // Whatever your app is supposed to do
    }
}

高级锁定

package YOUR_PACKAGE_NAME;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

/**
 * Minimal reproducible example (MRE) - Example of a more advanced lock system.
 * @author Remzi Cavdar - ict@remzi.info - <a href="https://github.com/Remzi1993">@Remzi1993</a>
 */
public class Main {
    public static void main(String[] args) {
        /*
         * Prevents the user of starting multiple instances of the application.
         * This is done by creating a temporary file in the app directory.
         * The temp file should be excluded from git and is called App.lock in this example.
         */
        final File FILE = new File("App.lock");

        if (FILE.exists()) {
            System.err.println("The application is already running!");
            return;
        }

        try (
                FileOutputStream fileOutputStream = new FileOutputStream(FILE);
                FileChannel channel = fileOutputStream.getChannel();
                FileLock lock = channel.lock()
        ) {
            System.out.println("Starting application");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        /*
         * Register a shutdown hook to delete the lock file when the application is closed. Even when forcefully closed
         * with the task manager. (Tested on Windows 11 with JavaFX 19)
         */
        FILE.deleteOnExit();
        // Whatever your app is supposed to do
    }
}

测试

  • 测试日期:2022年10月31日
  • 测试操作系统:Windows 11 - 版本21H2(OS Build 22000.1098)
  • 测试使用的软件环境:OpenJDK 19 - Eclipse Temurin JDK with Hotspot 19+36(x64)

我关闭了应用程序,并在Windows任务管理器中强制关闭了应用程序,两次锁定文件似乎都被删除了。


0

你可以写出类似这样的内容。

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

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

这对我很有效。


0
如果应用程序有 GUI,则可以使用 Java Web Start 启动它。提供给 Web Start 的 JNLP API 提供了 SingleInstanceService。这是我关于SingleInstanceService演示

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接