每个用户只有一个实例的Java应用程序

每个用户只有一个实例的Java应用程序,java,java-8,install4j,Java,Java 8,Install4j,目前,我正在努力解决单实例JavaFX应用程序的问题,该应用程序使用install4j打包到一个.exe中。应用程序应在Windows终端服务器上运行,每个用户只能运行一个实例。也就是说,Alice和Bob可能使用应用程序的不同实例,但Alice可能只打开了一个实例 使用进程id编写锁文件不是一个可行的选项,因为应用程序的目标是Java 8,而Java 8不可能一致地检索进程id。打开套接字也不是一个理想的解决方案,因为同一主机上可能有多个实例。此外,我想如果某个应用程序在其服务器上随机打开套接

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

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

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

这就引出了两个问题:

  • install4j检查是如何工作的?(我找不到任何细节…)
  • 确保每个用户始终拥有一个实例的最佳解决方案是什么?(还应具有故障保护功能,例如从JVM崩溃中恢复)
  • 关于
    文件锁的可能性
    :由于不同的操作系统可能会以不同的方式处理文件锁,能否确保文件锁由整个系统上的一个JVM实例独占获取

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

    /create-i4j-log
    

    参数。

    如果希望应用程序在不同用户下并发运行,则套接字将有点问题

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

    大概是这样的:

     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这样的库。我自己没试过,但你可以试一试。它看起来很旧,但我想像这样的东西没有什么需要改变的,自从Java1.4以来NIO没有什么改变

    它位于Maven Central上,因此您可以轻松导入它。

    如果查看代码,您会发现它对文件锁也有相同的作用:

    我遇到了同样的问题,并像另一个答案一样使用
    文件锁解决了这个问题


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

    为什么锁文件需要有进程id?如果JVM崩溃,第二个进程无论如何都不会有相同的进程id,我无法删除锁文件。因此,除非用户手动删除锁文件,否则应用程序无法再次启动。一旦进程终止,操作系统将释放对文件的锁定。这是大多数操作系统的标准行为。您只需在启动时获取文件锁,而不管文件是否已存在(如果不存在,您只需创建它)。这取决于您如何使用文件进行锁定。您可以检查文件是否存在,也可以尝试获取锁。如果检查是否存在,则需要pid来确定进程是否仍处于活动状态。我补充了第三点我对文件锁定方法的担忧是的,通常的做法是检查文件是否存在,可能尝试删除它(这样你就可以额外确保没有任何进程试图读取它),然后重新创建并锁定它。谢谢你的回复。这让我得出这样的结论:在Windows中,信号量以某种方式是基于会话的?我们决定坚持使用install4j方法,接受RDP session和RemoteApps的两个实例的缺陷。此外,install4j是否记录了这一点?否则,您能否指出它在mac和linux上的工作原理以供参考?除非这是一个实现细节,并且很可能会更改是,否则这是基于Windows的会话。在macOS和Unix上,它是基于用户的,并使用文件锁定。@IngoKegel我们是否有可能在install4j中获得基于用户的信号量,以启用“每个用户一个实例”方案?我希望使用install4j,因为launcher API已经允许获取参数(startupPerformed)。目前我是手动操作的(见下面的答案)@AlexSuzuki我已经将此添加到我们9,0版的问题跟踪器中了是的,我以前看过JUnique,但因为它看起来没有维护,所以放弃了它。然而,你可能是对的,没有多少事情要做。不过,既然Java应用程序应该是平台独立的,那么,
    FileLock
    方法是否有可能在某些平台上不起作用呢?例如,如果文件锁被视为advice(就像JavaDoc所暗示的那样)?@Rosso您针对的是哪些平台?我想如果你想100%确定你必须在你想要支持的平台上测试它。是的,它确实在某个平台上这么说