如何实现单实例Java应用程序?

Fua*_* S. 87 java single-instance

有时我看到许多应用程序,如msn,windows media player等是单实例应用程序(当用户执行应用程序运行时,将不会创建新的应用程序实例).

在C#中,我使用了Mutex类,但我不知道如何在Java中执行此操作.

小智 62

我在main方法中使用以下方法.这是我见过的最简单,最强大,最少侵入性的方法所以我认为我会分享它.

private static boolean lockInstance(final String lockFile) {
    try {
        final File file = new File(lockFile);
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
        log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)

  • 但是如果电源死机并且计算机在没有运行关机钩的情况下关闭会发生什么呢?该文件将保持不变,应用程序将无法访问. (5认同)
  • @PetrHudeček没关系.无论应用程序如何结束,文件锁都将被释放.如果它不是正确的关闭,那么这甚至可以让应用程序在下次运行时实现这一点.无论如何:锁是重要的,而不是文件本身的存在.如果文件仍然存在,则应用程序仍会启动. (5认同)
  • 是否真的有必要手动释放文件锁并在关机时关闭文件?当过程死亡时,这不会自动发生吗? (2认同)

Von*_*onC 60

如果我相信这篇文章,请:

让第一个实例尝试在localhost接口上打开侦听套接字.如果它能够打开套接字,则假定这是要启动的应用程序的第一个实例.如果不是,则假设此应用程序的实例已在运行.新实例必须通知现有实例尝试启动,然后退出.现有实例在接收通知后接管,并向处理该操作的侦听器触发事件​​.

注意:Ahe在评论中提到使用InetAddress.getLocalHost()可能很棘手:

  • 它在DHCP环境中无法正常工作,因为返回的地址取决于计算机是否具有网络访问权限.
    解决方案是打开连接InetAddress.getByAddress(new byte[] {127, 0, 0, 1}) ;
    可能与bug 4435662有关.
  • 我还发现了错误4665037报告的预期结果getLocalHost:返回机器的IP地址,与实际结果:返回127.0.0.1.

令人惊讶的是在Linux上获得getLocalHost回报127.0.0.1但在Windows上却没有.


或者你可以使用 ManagementFactory对象.正如解释在这里:

getMonitoredVMs(int processPid)方法接收当前应用程序PID作为参数,并捕获从命令行调用的应用程序名称,例如,应用程序从c:\java\app\test.jar路径启动,然后值变量为" c:\\java\\app\\test.jar".这样,我们将在下面代码的第17行捕获应用程序名称.
之后,我们在JVM中搜索具有相同名称的另一个进程,如果我们找到它并且应用程序PID不同,则意味着它是第二个应用程序实例.

JNLP也提供了一个 SingleInstanceListener

  • 请注意,第一个解决方案有一个错误.我们最近发现`InetAddress.getLocalHost()`在DHCP环境中无法正常工作,因为返回的地址取决于计算机是否具有网络访问权限.解决方案是打开与`InetAddress.getByAddress(new byte [] {127,0,0,1}};`的连接. (3认同)
  • 另请参阅[*使用SingleInstanceService服务*](http://docs.oracle.com/javase/7/docs/technotes/guides/javaws/developersguide/examples.html#SingleInstanceService). (3认同)
  • @Ahe:优点.我在编辑的答案中包含了您的评论以及Oracle-Sun错误报告参考. (2认同)
  • 根据JavaDoc`InetAddress.getByName(null)`返回循环返回接口的地址.我想这比手动指定127.0.0更好,因为理论上这应该也适用于仅IPv6的环境. (2认同)

And*_*son 8

如果应用程序.有一个GUI,用JWS启动它并使用SingleInstanceService.看演示.用于(演示和.)示例代码的SingleInstanceService.

  • 另请注意,似乎可以将新实例及其参数通知正在运行的实例,从而可以轻松地与此类程序进行通信。 (2认同)

小智 6

是的,对于eclipse来说,这是一个非常不错的答案.下面的RCP eclipse单实例应用程序是我的代码

在application.java中

if(!isFileshipAlreadyRunning()){
        MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running.  Exiting.");
        return IApplication.EXIT_OK;
    } 


private static boolean isFileshipAlreadyRunning() {
    // socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
    // but this one is really great
    try {
        final File file = new File("FileshipReserved.txt");
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        //log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
       // log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*Day 5

我们使用文件锁定(在用户的app数据目录中获取魔术文件的独占锁定),但我们主要感兴趣的是防止多个实例运行.

如果你试图让第二个实例将命令行args等传递给第一个实例,那么在localhost上使用套接字连接就会一举两得.一般算法:

  • 在启动时,尝试在localhost上的端口XXXX上打开侦听器
  • 如果失败,打开一个写入localhost上的端口并发送命令行args,然后关闭
  • 否则,在localhost上侦听端口XXXXX.接收命令行参数时,请像处理该命令行启动应用程序一样处理它们.


Iko*_*kon 5

我找到了一个解决方案,有点卡通化的解释,但在大多数情况下仍然有效.它使用普通的旧锁文件创建东西,但是在一个完全不同的视图中:

http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html

我认为这对那些拥有严格防火墙设置的人有帮助.


kol*_*bok 5

您可以使用JUnique库.它为运行单实例java应用程序提供支持,并且是开源的.

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

JUnique库可用于防止用户同时运行同一Java应用程序的更多实例.

JUnique实现了由同一用户启动的所有JVM实例之间共享的锁和通信通道.

public static void main(String[] args) {
    String appId = "myapplicationid";
    boolean alreadyRunning;
    try {
        JUnique.acquireLock(appId, new MessageHandler() {
            public String handle(String message) {
                // A brand new argument received! Handle it!
                return null;
            }
        });
        alreadyRunning = false;
    } catch (AlreadyLockedException e) {
        alreadyRunning = true;
    }
    if (!alreadyRunning) {
        // Start sequence here
    } else {
        for (int i = 0; i < args.length; i++) {
            JUnique.sendMessage(appId, args[0]));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在引擎盖下,它在%USER_DATA%/.junique文件夹中创建文件锁,并在随机端口为每个允许在Java应用程序之间发送/接收消息的唯一appId创建服务器套接字.