Java 每个用户具有单个实例的应用程序
Java Application with Single Instance per User
目前我正在努力解决单个实例 JavaFX 应用程序的问题,使用 install4j 打包到 .exe 中。该应用程序应该 运行 在 Windows 终端服务器上并且每个用户应该只能 运行 它的一个实例。意思是,Alice 和 Bob 可以使用不同的应用程序实例,但 Alice 可能只打开一个实例.
使用进程 ID 写入锁定文件不是一个可行的选择,因为应用程序的目标是 Java 8,它没有一致的可能性来检索进程 ID。打开套接字也不是理想的解决方案,因为同一主机上可能有许多实例。此外,我想如果某些应用程序在他们的服务器上随机打开套接字,管理员不会那么高兴...
因为我正在使用 install4j 来打包应用程序,所以我切换了 'single instance only' 功能,当通过完整的 RDP 会话连接时,它似乎 运行 很好.但是,可以使用 RemoteApp 功能部署应用程序,该功能以某种方式绕过 install4j 的检查机制,允许一个实例在 RDP 会话中启动,另一个实例通过使用 RemoteApp 启动。
这引出了两个问题:
- install4j 检查如何工作? (我找不到任何细节...)
- 确保每个用户始终只有一个实例的最佳解决方案是什么? (并且也是故障安全的,例如从 JVM 崩溃中恢复)
- 关于
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。
目前我正在努力解决单个实例 JavaFX 应用程序的问题,使用 install4j 打包到 .exe 中。该应用程序应该 运行 在 Windows 终端服务器上并且每个用户应该只能 运行 它的一个实例。意思是,Alice 和 Bob 可以使用不同的应用程序实例,但 Alice 可能只打开一个实例.
使用进程 ID 写入锁定文件不是一个可行的选择,因为应用程序的目标是 Java 8,它没有一致的可能性来检索进程 ID。打开套接字也不是理想的解决方案,因为同一主机上可能有许多实例。此外,我想如果某些应用程序在他们的服务器上随机打开套接字,管理员不会那么高兴...
因为我正在使用 install4j 来打包应用程序,所以我切换了 'single instance only' 功能,当通过完整的 RDP 会话连接时,它似乎 运行 很好.但是,可以使用 RemoteApp 功能部署应用程序,该功能以某种方式绕过 install4j 的检查机制,允许一个实例在 RDP 会话中启动,另一个实例通过使用 RemoteApp 启动。
这引出了两个问题:
- install4j 检查如何工作? (我找不到任何细节...)
- 确保每个用户始终只有一个实例的最佳解决方案是什么? (并且也是故障安全的,例如从 JVM 崩溃中恢复)
- 关于
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。