【问题标题】:Java Application with Single Instance per User每个用户具有单个实例的 Java 应用程序
【发布时间】:2018-10-22 14:24:14
【问题描述】:

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

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

当我使用 install4j 打包应用程序时,我切换了“仅单个实例”功能,该功能在通过完整 RDP 会话连接时似乎运行良好。但是,可以使用 RemoteApp 功能部署应用程序,该功能以某种方式绕过 install4j 的检查机制,允许在 RDP 会话中启动一个实例,并使用 RemoteApp 启动另一个实例。

这引出了两个问题:

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

【问题讨论】:

  • 为什么锁文件需要有进程id?无论如何,第二个进程都不会具有相同的进程 ID
  • 如果 JVM 崩溃,我无法删除锁定文件。因此,应用程序无法再次启动,除非用户手动删除锁定文件
  • 并非如此。一旦进程终止,操作系统将释放文件上的锁定。这是大多数操作系统上的标准行为。您只需要在启动时获取文件锁定,无论文件是否已经存在(如果不存在,您只需创建它)。
  • 这取决于您如何使用文件进行锁定。您可以检查文件是否存在,或者尝试获取锁。如果您检查是否存在,则需要 pid 来确定该进程是否仍然存在。我补充了第三点我对文件锁定方法的担忧
  • 是的,通常的做法是检查文件是否存在,可能尝试删除它(这样你就可以更加确定没有进程甚至试图从中读取),然后重新创建并锁定它。

标签: java java-8 install4j


【解决方案1】:

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

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

类似这样的:

 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

【讨论】:

  • 是的,我以前看过 JUnique,但因为它似乎没有维护而放弃了它。但是,您可能是对的,没有什么可做的。我会试一试的。
  • 但是,由于 Java 应用程序应该独立于平台:有没有可能FileLock 方法在某些平台上不起作用?例如,如果文件锁定被视为建议(就像 JavaDoc 暗示的那样)?
  • @Rosso 您针对哪些平台?我想如果你想 100% 确定你必须在你想要支持的平台上测试它。是的,它确实说在某些平台上文件锁定是不可能的,但我想这也是因为文件锁定是本机的,所以他们不可能在所有可能的 JVM 实现上保证它。另一方面,我不知道有任何现代操作系统不能以这种方式工作,但我可能是错的。
  • 这是一个比较笼统的问题。对于我的用例(Windows/mac),它非常适合,只是想知道那里是否有什么众所周知的
【解决方案2】:

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

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

论据。

【讨论】:

  • 感谢您的回复。这使我得出结论,信号量在某种程度上是基于 Windows 的会话?我们决定坚持使用 install4j 方法,接受 RDP 会话和 RemoteApp 两个实例的缺陷
  • 此外,这是否记录在 install4j 的某个地方?否则,您能否指出它在 mac 和 linux 上的工作原理以供参考?除非这是一个实现细节并且很可能会改变
  • 是的,这是基于 Windows 的会话。在 macOS 和 Unix 上,它基于用户并使用文件锁定。
  • @IngoKegel 我们是否有机会在 install4j 中获得基于用户的信号量,以启用“每个用户单个实例”场景?我很乐意为此使用 install4j,因为启动器 API 已经允许获取参数(startupPerformed)。目前我是手动完成的(见下面的答案)
  • @AlexSuzuki 我已将此添加到我们 9,0 的问题跟踪器中
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2018-09-09
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多