Java 每个用户具有单个实例的应用程序

Java Application with Single Instance per User

目前我正在努力解决单个实例 JavaFX 应用程序的问题,使用 install4j 打包到 .exe 中。该应用程序应该 运行 在 Windows 终端服务器上并且每个用户应该只能 运行 它的一个实例。意思是,AliceBob 可以使用不同的应用程序实例,但 Alice 可能只打开一个实例.

使用进程 ID 写入锁定文件不是一个可行的选择,因为应用程序的目标是 Java 8,它没有一致的可能性来检索进程 ID。打开套接字也不是理想的解决方案,因为同一主机上可能有许多实例。此外,我想如果某些应用程序在他们的服务器上随机打开套接字,管理员不会那么高兴...

因为我正在使用 install4j 来打包应用程序,所以我切换了 'single instance only' 功能,当通过完整的 RDP 会话连接时,它似乎 运行 很好.但是,可以使用 RemoteApp 功能部署应用程序,该功能以某种方式绕过 install4j 的检查机制,允许一个实例在 RDP 会话中启动,另一个实例通过使用 RemoteApp 启动。

这引出了两个问题:

  1. install4j 检查如何工作? (我找不到任何细节...)
  2. 确保每个用户始终只有一个实例的最佳解决方案是什么? (并且也是故障安全的,例如从 JVM 崩溃中恢复)
  3. 关于FileLock的可能性:不同的操作系统对文件锁的处理方式可能不同,能否保证文件锁在整个系统上由一个JVM实例独占获取?

至于 1:在 Windows 上,install4j 启动器在 Windows API 中使用 CreateSemaphore 函数创建一个信号量。您可以通过使用

从命令行执行启动程序来检查信号量的名称
/create-i4j-log

参数。

如果您希望应用程序在不同用户下同时 运行,套接字会有点问题。

可以选择使用 NIO FileLock。您在用户的目录下创建该文件,以便其他用户可以拥有自己的锁定文件。这里要做的关键是,如果文件已经存在,仍然尝试获取文件锁,方法是在重新创建文件之前尝试将其删除。这样,如果应用程序崩溃但文件仍然存在,您仍然可以获取对它的锁定。请记住,OS 应该在进程终止时释放所有锁、打开的文件句柄和系统资源。

像这样:

 public ExclusiveApplicationLock
     throws Exception {

   private final File file;
   private final FileChannel channel;
   private final FileLock lock;

   private ExclusiveApplicationLock()  {

       String homeDir = System.getProperty("user.home");

       file = new File(homeDir + "/.myapp", app.lock");
       if (file.exists()) {
          file.delete();
       }

       channel = new RandomAccessFile(file, "rw").getChannel();
       lock = channel.tryLock();
       if (lock == null)  {
          channel.close();
          throw new RuntimeException("Application already running.");
       }

       Runtime.getRuntime().addShutdownHook(new Thread(() -> releaseLock());            
  }

  private void releaseLock() {
    try {
      if (lock != null) {
        lock.release();
        channel.close();
        file.delete();
      }
    }
    catch (Exception ex) {
       throw new RuntimeException("Unable to release application process lock", ex);
    }
  }
}

另一种选择是使用像 Junique 这样为您执行此操作的库。我自己没试过,但你可以试一试。它看起来很旧,但我想像这样的东西不需要改变太多,自 Java 1.4 以来 NIO 没有太大变化。

http://www.sauronsoftware.it/projects/junique/

它位于 Maven Central 上,因此您可以轻松导入它。 https://mvnrepository.com/artifact/it.sauronsoftware/junique/1.0.4

如果您查看代码,您会发现它对文件锁做了同样的事情: https://github.com/poolborges/it.sauronsoftware.junique/blob/master/src/main/java/it/sauronsoftware/junique/JUnique.java

我遇到了同样的问题,并像其他答案一样使用 FileLock 解决了它。

在我的例子中,传递给已启动进程的参数需要转发给第一个进程。为此,我使用了命名管道,其名称中包含用户名。第一个进程在 \.\pipe\app_$USER 处创建命名管道。如果同一个 exe 由同一个用户启动,它会被 FileLock 检测到,并且通过命名管道传递 agruments。