【问题标题】:How to implement a single instance Java application?如何实现单实例 Java 应用程序?
【发布时间】:2008-10-07 03:54:57
【问题描述】:

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

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

【问题讨论】:

标签: java single-instance


【解决方案1】:

我在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;
}

【讨论】:

  • 桌面应用程序的参数“lockFile”应该是什么?应用程序 jar 文件名?没有jar文件只有一些类文件怎么样?
  • 真的有必要手动释放文件锁并在关机时关闭文件吗?当进程终止时,这不会自动发生吗?
  • 但是如果断电并且计算机在没有运行关机挂钩的情况下关闭会发生什么?该文件将持续存在,应用程序将无法启动。
  • @PetrHudeček 没关系。无论应用程序如何结束,文件锁都会被释放。如果不是正确关闭,那么这甚至有利于应用程序在下次运行时实现这一点。在任何情况下:锁才是重要的,而不是文件本身的存在。如果文件仍然存在,则应用程序仍将启动。
  • @Robert:感谢您的解决方案,从那以后我一直在使用它。刚才,我将它扩展为还与另一个实例尝试启动的现有实例进行通信 - 使用文件夹 WatchService! stackoverflow.com/a/36772436/3500521
【解决方案2】:

如果我相信article,作者:

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

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

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

令人惊讶的是,getLocalHost 在 Linux 上返回 127.0.0.1 而在 Windows 上却没有。


或者你可以使用ManagementFactory 对象。正如here解释的那样:

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}); 的连接。
  • @Ahe:好点。我已在我编辑的答案中包含了您的评论以及 Oracle-Sun 错误报告参考。
  • 根据JavaDocInetAddress.getByName(null)返回环回接口的地址。我想这比手动指定 127.0.0.1 更好,因为理论上这也应该在仅限 IPv6 的环境中工作。
  • @Puce 当然,没问题:我已经恢复了这些链接。
【解决方案3】:

如果应用程序。有一个 GUI,使用 JWS 启动它并使用 SingleInstanceService

更新

Java 插件(applet 和 JWS 应用程序都需要)已被 Oracle 弃用并从 JDK 中删除。浏览器制造商已将其从浏览器中删除。

所以这个答案已经失效了。仅将其留在这里以警告查看旧文档的人。

【讨论】:

  • 另请注意,似乎可以将新实例及其参数通知正在运行的实例,从而可以轻松地与此类程序进行通信。
【解决方案4】:

是的,对于 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;
}

【讨论】:

    【解决方案5】:

    我们为此使用文件锁定(在用户的应用数据目录中的魔术文件上获取排他锁),但我们主要感兴趣的是防止多个实例运行。

    如果您试图让第二个实例将命令行参数等...传递给第一个实例,那么在 localhost 上使用套接字连接将是一块石头杀死两只鸟。通用算法:

    • 启动时,尝试在本地主机上的端口 XXXX 上打开侦听器
    • 如果失败,在 localhost 上打开该端口的写入器并发送命令行参数,然后关闭
    • 否则,请在 localhost 上的端口 XXXXX 上侦听。当接收到命令行参数时,处理它们,就好像应用程序是使用该命令行启动的一样。

    【讨论】:

      【解决方案6】:

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

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

      我认为这对那些有严格防火墙设置的人会有所帮助。

      【讨论】:

      • 是的,这是一个好方法,因为如果应用程序崩溃,锁就会被释放:)
      【解决方案7】:

      您可以使用 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]));
              }
          }
      }
      

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

      【讨论】:

      • 我可以使用它来防止网络中的多个 java 应用程序实例吗? aka,我的整个网络中只允许我的应用程序的一个实例
      【解决方案8】:

      在 Windows 上,您可以使用launch4j

      【讨论】:

        【解决方案9】:

        J2SE 5.0 或更高版本支持的ManagementFactory 类detail

        但现在我使用 J2SE 1.4,我发现了这个 http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ 但我从未测试过。你怎么看?

        【讨论】:

        【解决方案10】:

        您可以打开一个内存映射文件,然后查看该文件是否已经打开。如果已经打开,可以从main返回。

        其他方法是使用锁定文件(标准 unix 做法)。另一种方法是在检查剪贴板中是否已有内容后,在 main 启动时将内容放入剪贴板。

        否则,您可以在侦听模式下打开套接字(ServerSocket)。首先尝试连接到hte socket;如果无法连接,则打开一个 serversocket。如果你连接了,那么你就知道另一个实例已经在运行了。

        因此,几乎任何系统资源都可用于了解应用正在运行。

        BR, ~A

        【讨论】:

        • 你有这些想法的代码吗?另外,如果我希望用户启动一个新实例,它将关闭所有以前的实例怎么办?
        【解决方案11】:

        您可以尝试使用 Preferences API。它独立于平台。

        【讨论】:

        • 我喜欢这个想法,因为 API 很简单,但也许某些病毒扫描程序不希望您更改注册表,因此您会遇到与在具有软件防火墙的系统上使用 RMI 类似的问题......不是当然。
        • @Cal 但同样的问题是文件更改/锁定/等...你不觉得吗?
        【解决方案12】:

        我为此使用了套接字,取决于应用程序是在客户端还是服务器端,行为会有所不同:

        • 客户端:如果实例已经存在(我无法监听特定端口),我将传递应用程序参数并退出(您可能希望在前一个实例中执行一些操作)如果不存在,我将启动应用程序。
        • 服务器端:如果实例已经存在,我将打印一条消息并退出,否则我将启动应用程序。

        【讨论】:

          【解决方案13】:

          限制单台机器甚至整个网络上实例数量的更通用方法是使用多播套接字。

          使用多播套接字,您可以将消息广播到应用程序的任意数量的实例,其中一些实例可以位于公司网络上的物理远程计算机上。

          通过这种方式,您可以启用多种类型的配置,以控制诸如

          • 每台机器一个或多个实例
          • 每个网络一个或多个实例(例如控制客户端站点上的安装)

          Java 的多播支持是通过 java.net 包 实现的,其中 MulticastSocketDatagramSocket 是主要工具。

          注意:MulticastSocket 不保证数据包的传送,因此您应该使用构建在多播套接字之上的工具,例如JGroups。 JGroups 确实保证所有数据的交付。它是一个 jar 文件,具有非常简单的 API。

          JGroups 已经存在了一段时间,并且在行业中有一些令人印象深刻的用途,例如它支持 JBoss 的集群机制,将数据广播到集群的所有实例。

          要使用 JGroups,限制应用程序实例的数量(在机器或网络上,比如说:客户购买的许可证数量)在概念上非常简单:

          • 在您的应用程序启动时,每个实例都会尝试加入一个命名组,例如“My Great App Group”。您将已将此群组配置为允许 0、1 或 N 个成员
          • 当组成员数量大于您为其配置的数量时..您的应用应该拒绝启动。

          【讨论】:

            【解决方案14】:

            Unique4j 库可用于运行 Java 应用程序的单个实例并传递消息。您可以在https://github.com/prat-man/unique4j 看到它。它支持 Java 1.6+。

            它使用文件锁和动态端口锁的组合来检测实例之间并进行通信,其主要目标是只允许一个实例运行。

            下面是一个简单的例子:

            import tk.pratanumandal.unique4j.Unique4j;
            import tk.pratanumandal.unique4j.exception.Unique4jException;
            
            public class Unique4jDemo {
            
                // unique application ID
                public static String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
            
                public static void main(String[] args) throws Unique4jException, InterruptedException {
            
                    // create unique instance
                    Unique4j unique = new Unique4j(APP_ID) {
                        @Override
                        public void receiveMessage(String message) {
                            // display received message from subsequent instance
                            System.out.println(message);
                        }
            
                        @Override
                        public String sendMessage() {
                            // send message to first instance
                            return "Hello World!";
                        }
                    };
            
                    // try to obtain lock
                    boolean lockFlag = unique.acquireLock();
            
                    // sleep the main thread for 30 seconds to simulate long running tasks
                    Thread.sleep(30000);
            
                    // try to free the lock before exiting program
                    boolean lockFreeFlag = unique.freeLock();
            
                }
            
            }
            

            免责声明:我创建并维护了 Unique4j 库。

            【讨论】:

              【解决方案15】:
              公共类 SingleInstance { public static final String LOCK = System.getProperty("user.home") + File.separator + "test.lock"; public static final String PIPE = System.getProperty("user.home") + File.separator + "test.pipe"; 私有静态 JFrame 框架 = null; 公共静态无效主要(字符串[]参数){ 尝试 { FileChannel lockChannel = new RandomAccessFile(LOCK, "rw").getChannel(); 文件锁 flk = null; 尝试 { flk = lockChannel.tryLock(); } 捕捉(可投掷的 t){ t.printStackTrace(); } if (flk == null || !flk.isValid()) { System.out.println("已经运行,给管道留言并退出..."); FileChannel pipeChannel = null; 尝试 { pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel(); MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1); bb.put(0, (byte)1); bb.force(); } 捕捉(可投掷的 t){ t.printStackTrace(); } 最后 { 如果(管道通道!= null){ 尝试 { pipeChannel.close(); } 捕捉(可投掷的 t){ t.printStackTrace(); } } } System.exit(0); } //这里我们不释放锁关闭通道, // 这将在应用程序崩溃或正常关闭后完成。 SwingUtilities.invokeLater( 新的可运行(){ 公共无效运行(){ createAndShowGUI(); } } ); FileChannel pipeChannel = null; 尝试 { pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel(); MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1); 而(真){ 字节 b = bb.get(0); 如果 (b > 0) { bb.put(0, (byte)0); bb.force(); SwingUtilities.invokeLater( 新的可运行(){ 公共无效运行(){ frame.setExtendedState(JFrame.NORMAL); frame.setAlwaysOnTop(true); frame.toFront(); frame.setAlwaysOnTop(false); } } ); } 线程.sleep(1000); } } 捕捉(可投掷的 t){ t.printStackTrace(); } 最后 { 如果(管道通道!= null){ 尝试 { pipeChannel.close(); } 捕捉(可投掷的 t){ t.printStackTrace(); } } } } 捕捉(可投掷的 t){ t.printStackTrace(); } } 公共静态无效createAndShowGUI(){ 框架 = 新的 JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(800, 650); frame.getContentPane().add(new JLabel("主窗口", SwingConstants.CENTER), BorderLayout.CENTER); frame.setLocationRelativeTo(null); frame.setVisible(true); } }

              【讨论】:

                【解决方案16】:

                编辑:不使用这种 WatchService 方法,可以使用一个简单的 1 秒计时器线程来检查 indicatorFile.exists() 是否存在。删除它,然后将应用程序带到Front()。

                编辑:我想知道为什么这被否决了。这是迄今为止我见过的最好的解决方案。例如。如果另一个应用程序碰巧已经在监听该端口,则服务器套接字方法会失败。

                只需下载 Microsoft Windows SysinternalsTCPView(或使用 netstat),启动它,按“状态”排序,查找显示“LISTENING”的行块,选择一个远程地址为您的计算机名称的行,输入该端口进入您的新套接字()解决方案。在我的实现中,我每次都会产生失败。这是合乎逻辑的,因为它是该方法的基础。或者我没有得到关于如何实现这一点的信息?

                如果我错了,请告诉我!

                我的观点——如果可能的话,我要求你反驳——建议开发人员在生产代码中使用一种方法,这种方法在大约 60000 例中至少有 1 例会失败。如果这种观点碰巧是正确的,那么绝对不会提出的解决方案没有这个问题,并因其代码量而被否决和批评。

                socket方法的缺点比较:

                • 如果选择了错误的彩票(端口号)则失败。
                • 在多用户环境中失败:只有一个用户可以同时运行应用程序。 (我的方法必须稍作更改才能在用户树中创建文件,但这很简单。)
                • 如果防火墙规则过于严格,则会失败。
                • 让可疑用户(我在野外遇到过)想知道当您的文本编辑器声明服务器套接字时您在搞什么恶作剧。

                我刚刚有了一个很好的想法,即如何以一种适用于每个系统的方式来解决新实例到现有实例的 Java 通信问题。所以,我在大约两个小时内完成了这门课。像魅力一样工作:D

                它基于Robert 的文件锁定方法(也在此页面上),从那时起我就一直在使用它。告诉已经运行的实例另一个实例试图启动(但没有)...创建一个文件并立即删除,第一个实例使用 WatchService 检测此文件夹内容更改。考虑到问题的根本性,我不敢相信这显然是一个新想法。

                这可以很容易地更改为只是 create 而不是删除文件,然后可以将信息放入其中以便适当的实例可以评估,例如命令行参数 - 然后适当的实例可以执行删除。就个人而言,我只需要知道何时恢复我的应用程序的窗口并将其发送到前面。

                使用示例:

                public static void main(final String[] args) {
                
                    // ENSURE SINGLE INSTANCE
                    if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
                        System.exit(0);
                    }
                
                    // launch rest of application here
                    System.out.println("Application starts properly because it's the only instance.");
                }
                
                private static void otherInstanceTriedToLaunch() {
                    // Restore your application window and bring it to front.
                    // But make sure your situation is apt: This method could be called at *any* time.
                    System.err.println("Deiconified because other instance tried to start.");
                }
                

                课程如下:

                package yourpackagehere;
                
                import javax.swing.*;
                import java.io.File;
                import java.io.IOException;
                import java.io.RandomAccessFile;
                import java.nio.channels.FileLock;
                import java.nio.file.*;
                
                
                
                
                /**
                 * SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
                 * <p>
                 * (file lock single instance solution by Robert https://stackoverflow.com/a/2002948/3500521)
                 */
                public enum SingleInstanceChecker {
                
                    INSTANCE; // HAHA! The CONFUSION!
                
                
                    final public static int POLLINTERVAL = 1000;
                    final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
                    final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");
                
                
                    private boolean hasBeenUsedAlready = false;
                
                
                    private WatchService watchService = null;
                    private RandomAccessFile randomAccessFileForLock = null;
                    private FileLock fileLock = null;
                
                
                    /**
                     * CAN ONLY BE CALLED ONCE.
                     * <p>
                     * Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
                     * installed in that case.
                     * <p>
                     * Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
                     * the temp file the return value will be true or false. This approach even works even if the virtual machine
                     * process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
                     * the file will still exist. (Thanks to Robert https://stackoverflow.com/a/2002948/3500521 for that solution!)
                     * <p>
                     * Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
                     * is, it lacks some fundamental features. Don't worry, it has only been 25 years, it'll sure come eventually.
                     *
                     * @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
                     *                                             changes the detect-file), the code will be executed. Could be used to
                     *                                             bring the current (=old=only) instance to front. If null, then the
                     *                                             watcher will not be installed at all, nor will the trigger file be
                     *                                             created. (Null means that you just don't want to make use of this
                     *                                             half of the class' purpose, but then you would be better advised to
                     *                                             just use the 24 line method by Robert.)
                     *                                             <p>
                     *                                             BE CAREFUL with the code: It will potentially be called until the
                     *                                             very last moment of the program's existence, so if you e.g. have a
                     *                                             shutdown procedure or a window that would be brought to front, check
                     *                                             if the procedure has not been triggered yet or if the window still
                     *                                             exists / hasn't been disposed of yet. Or edit this class to be more
                     *                                             comfortable. This would e.g. allow you to remove some crappy
                     *                                             comments. Attribution would be nice, though.
                     * @param executeOnAWTEventDispatchThread      Convenience function. If false, the code will just be executed. If
                     *                                             true, it will be detected if we're currently on that thread. If so,
                     *                                             the code will just be executed. If not so, the code will be run via
                     *                                             SwingUtilities.invokeLater().
                     * @return if this is the only instance
                     */
                    public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
                
                        if (hasBeenUsedAlready) {
                            throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
                        }
                        hasBeenUsedAlready = true;
                
                        final boolean ret = canLockFileBeCreatedAndLocked();
                
                        if (codeToRunIfOtherInstanceTriesToStart != null) {
                            if (ret) {
                                // Only if this is the only instance, it makes sense to install a watcher for additional instances.
                                installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
                            } else {
                                // Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
                                //
                                // Regarding "codeToRunIfOtherInstanceTriesToStart != null":
                                // While creation/deletion of the file concerns THE OTHER instance of the program,
                                // making it dependent on the call made in THIS instance makes sense
                                // because the code executed is probably the same.
                                createAndDeleteOtherInstanceWatcherTriggerFile();
                            }
                        }
                
                        optionallyInstallShutdownHookThatCleansEverythingUp();
                
                        return ret;
                    }
                
                
                    private void createAndDeleteOtherInstanceWatcherTriggerFile() {
                
                        try {
                            final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
                            randomAccessFileForDetection.close();
                            Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                
                
                    private boolean canLockFileBeCreatedAndLocked() {
                
                        try {
                            randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
                            fileLock = randomAccessFileForLock.getChannel().tryLock();
                            return fileLock != null;
                        } catch (Exception e) {
                            return false;
                        }
                    }
                
                
                    private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
                
                        // PREPARE WATCHSERVICE AND STUFF
                        try {
                            watchService = FileSystems.getDefault().newWatchService();
                        } catch (IOException e) {
                            e.printStackTrace();
                            return;
                        }
                        final File appFolder = new File("").getAbsoluteFile(); // points to current folder
                        final Path appFolderWatchable = appFolder.toPath();
                
                
                        // REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
                        try {
                            appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
                        } catch (IOException e) {
                            e.printStackTrace();
                            return;
                        }
                
                
                        // INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE'S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
                        final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
                        t.setDaemon(true);
                        t.setName("directory content change watcher");
                        t.start();
                    }
                
                
                    private void optionallyInstallShutdownHookThatCleansEverythingUp() {
                
                        if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
                            return;
                        }
                
                        final Thread shutdownHookThread = new Thread(() -> {
                            try {
                                if (fileLock != null) {
                                    fileLock.release();
                                }
                                if (randomAccessFileForLock != null) {
                                    randomAccessFileForLock.close();
                                }
                                Files.deleteIfExists(LOCKFILE.toPath());
                            } catch (Exception ignore) {
                            }
                            if (watchService != null) {
                                try {
                                    watchService.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                        Runtime.getRuntime().addShutdownHook(shutdownHookThread);
                    }
                
                
                    private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {
                
                        while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)
                
                            try {
                                Thread.sleep(POLLINTERVAL);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                
                
                            final WatchKey wk;
                            try {
                                wk = watchService.poll();
                            } catch (ClosedWatchServiceException e) {
                                // This situation would be normal if the watcher has been closed, but our application never does that.
                                e.printStackTrace();
                                return;
                            }
                
                            if (wk == null || !wk.isValid()) {
                                continue;
                            }
                
                
                            for (WatchEvent<?> we : wk.pollEvents()) {
                
                                final WatchEvent.Kind<?> kind = we.kind();
                                if (kind == StandardWatchEventKinds.OVERFLOW) {
                                    System.err.println("OVERFLOW of directory change events!");
                                    continue;
                                }
                
                
                                final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
                                final File file = watchEvent.context().toFile();
                
                
                                if (file.equals(DETECTFILE)) {
                
                                    if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
                                        codeToRunIfOtherInstanceTriesToStart.run();
                                    } else {
                                        SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
                                    }
                
                                    break;
                
                                } else {
                                    System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
                                }
                
                            }
                
                            wk.reset();
                        }
                    }
                
                }
                

                【讨论】:

                • 你不需要数百行代码来解决这个问题。 new ServerSocket() 有一个 catch 块就足够了,
                • @EJP 您指的是已接受的答案,还是您在说什么?我已经搜索了相当多的 x-platform no-extra-library 解决方案,它不会失败,例如因为某些套接字碰巧已经被 不同的 应用程序占用了。如果有解决方案 - 特别是像你所指的那样超级简单 - 那么我想知道它。
                • @EJP:我想再次问 1) 你指的是什么微不足道的解决方案,你一直像胡萝卜一样晃来晃去我的头,2) 如果它是接受的答案开头的套接字解​​决方案,如果我的一个或多个“套接字方法的缺点”要点适用,并且 3) 如果是这样,为什么尽管有这些缺点,你仍然会推荐这种方法而不是像我这样的方法。
                • @EJP:问题是你的声音有一定的分量,你肯定知道,但我有所有证据迫使我相信你的建议这是错误的。看,我并不是坚持我的解决方案是正确的,但我是一台循证机器。您是否没有看到您的职位赋予您对社区的责任,以填补开始的沟通中缺失的拼图?
                • @EJP:很遗憾,您没有做出任何反应,我将假设事实:服务器套接字解决方案的真相是它确实存在严重缺陷 ,而大多数选择它的原因可能是“其他人也使用它。”,或者他们可能被不负责任的人欺骗使用它。我您没有用必要的解释来尊重我们的部分原因是您无法理解/为什么您从未质疑过这种方法,并且您不想做出公开声明揭示了这一点。
                【解决方案17】:

                我为此编写了一个专用库 https://sanyarnd.github.io/applocker

                它基于文件通道锁定,因此不会阻塞端口号,也不会在断电的情况下死锁应用程序(一旦进程终止,通道就会被释放)。

                库本身是轻量级的,并且具有流畅的 API。

                它的灵感来自http://www.sauronsoftware.it/projects/junique/,但它是基于文件通道的。还有其他额外的新功能。

                【讨论】:

                  猜你喜欢
                  • 2012-03-20
                  • 2023-03-25
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-03-05
                  • 2017-04-24
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多