【问题标题】:How to make sure that only a single instance of a Java application is running?如何确保只有一个 Java 应用程序实例正在运行?
【发布时间】:2012-05-17 12:58:42
【问题描述】:

我希望我的应用程序检查其自身的另一个版本是否已经在运行。

例如demo.jar启动,用户点击再次运行,但第二个实例实现“哦等等,已经有一个demo.jar在运行”。并退出并显示一条消息。

【问题讨论】:

    标签: java mutex


    【解决方案1】:

    强制使用 ServerSocket 锁运行的程序的一个实例

    Java 代码。将其放入名为 Main.java 的文件中:

    import java.net.*;
    import java.io.*;
    public class Main{
      public static void main(String args[]){
        ServerSocket socket = null;
        try {
          socket = new ServerSocket(34567);
          System.out.println("Doing hard work for 100 seconds");
          try{ Thread.sleep(100000); } catch(Exception e){ }
          socket.close();
        }
        catch (IOException ex) {
          System.out.println("App already running, exiting...");
        }
        finally {
          if (socket != null)
              try{ socket.close(); } catch(Exception e){}
        }
      }
    }
    

    编译并运行它

    javac Main.java
    java Main
    

    正常情况下测试:

    运行程序。您有 100 秒的时间在另一个终端中再次运行该程序,它会说它已经在运行。然后等待 100 秒,它应该允许你在第二个终端运行它。

    用 kill -9 强制停止程序后测试它

    1. 在终端 1 中启动程序。
    2. 在 100 秒内从另一个终端杀死 -9 进程。
    3. 再次运行程序,允许运行。

    结论:

    当您的程序不再运行时,操作系统会清理套接字占用。所以你可以确定程序不会运行两次。

    缺点

    如果某个鬼鬼祟祟的人,或者某个顽皮的进程要绑定所有的端口,或者只是你的端口,那么你的程序将不会运行,因为它认为它已经在运行。

    【讨论】:

      【解决方案2】:

      您要查找的内容可能最好通过锁定文件来完成。锁定文件是指具有预定义位置且存在于您的互斥锁的文件。

      在程序启动时测试该文件是否存在,如果存在,则立即退出。在已知位置创建文件。如果您的程序正常退出,请删除锁定文件。

      最好的办法是您还可以使用 pid(进程 ID)填充文件,这样您就可以检测到未删除文件但特定于操作系统的异常退出。

      【讨论】:

      • 只是一个附录:如果系统有可用的 PID,我还没有尝试过更安全的 pid。我之前尝试过,但发现了一个小问题(只是误报)。现在这可能是特定于机器的。因此,当我的程序关闭时,它会删除该文件。该文件没有被立即删除(由于缓冲或其他原因),当我再次快速运行程序时,它会找到该文件(即使它已被删除)
      • 您可能希望为用户提供一种删除锁定文件的方法,以防应用程序崩溃并且不会自动删除它。
      • 可以,有没有办法让文件在重启时自动删除?
      • @JorisBolsens 将文件放在系统 tmp 目录中。我知道这会在 linux 重新启动时清除,我认为 Windows 也有类似的位置。
      • 好吧,无论如何我可以让它删除,即使我的 jar 崩溃了?
      【解决方案3】:

      简单而强大的测试解决方案。

          static File file;
          static FileChannel fileChannel;
          static FileLock lock;
          static boolean running = false;
      
          @SuppressWarnings("resource")
          public static boolean checkIfAlreadyRunning() throws IOException {
              file = new File(FilePath.FILEPATH + "az-client.lock");
              if (!file.exists()) {
                  file.createNewFile();
                  running = true;
              } else {
                  file.delete();
              }
      
              fileChannel = new RandomAccessFile(file, "rw").getChannel();
              lock = fileChannel.tryLock();
      
              if (lock == null) {
                  fileChannel.close();
                  return true;
              }
              ShutdownHook shutdownHook = new ShutdownHook();
              Runtime.getRuntime().addShutdownHook(shutdownHook);
      
              return running;
          }
      
          public static void unlockFile() {
              try {
                  if (lock != null)
                      lock.release();
                  fileChannel.close();
                  file.delete();
                  running = false;
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          static class ShutdownHook extends Thread {
              public void run() {
                  unlockFile();
              }
          }
      

      将这些方法放在某个 Util 类中,然后在启动主类之前检查是否已存在,然后向用户显示一些对话框,否则启动应用程序。即使您异常关闭 java 进程或您所做的任何事情,它也能正常工作。它健壮且高效,无需设置 DataGram 侦听器或其他任何东西...

      【讨论】:

      • 您的 FilePath.FILEPATH 不是来自 JDK,我的项目中没有可用的 Jenkins。此语句 file = new File(FilePath.FILEPATH + "az-client.lock"); 应更改为适用于我的情况。你能告诉我怎么做吗?
      • 什么?我没听懂问题
      • 只是好奇 - 如果文件存在(否则从 if (!file.exists()) 分支删除文件,然后尝试锁定它。如果文件存在并且是锁定?我觉得逻辑有点奇怪(如果文件被锁定,为什么要删除它?)...
      • 简单地创建一个文件并检查它怎么样:stackoverflow.com/a/27297037/1911652
      【解决方案4】:

      如果您使用 Mutex,从逻辑上讲,Mutex 需要可从任何运行“程序”副本的 JVM 访问。在 C 编程中,这可能是通过共享内存来实现的,但 Java 默认没有这样的东西。

      有了这种理解,就有很多方法可以实现您想要的。您可以在指定端口上打开服务器套接字(操作系统确保只有一个进程是服务器套接字的接收者,随后的打开失败)。

      您可以使用“锁定文件”,但它有点复杂,因为您需要使用的文件实际上是一个目录(并且它在很大程度上取决于目录创建对于您的文件系统是否是原子的,即使大多数目录创建都是)。如果系统管理员决定通过 NFS 运行您,那么事情会变得更加困难(如果不是不可能的话)。

      您还可以使用 JVM 和调试/JMI 做一些漂亮的技巧,前提是您可以以某种方式向自己保证所有相关 JVM 都以相同的配置启动(及时,这是一项不可能完成的任务)。

      其他人已经使用 exec 工具来运行等效的进程列表,但由于可能出现竞争条件(两个进程同时检查,但无法看到对方),这有点棘手。

      最后,服务器套接字路由可能是最稳定的,因为它被 TCP/IP 堆栈保证只绑定到一个进程(并且由操作系统调解)。也就是说,您必须刷新传入消息的套接字,这会带来其他安全问题的可能性。

      【讨论】:

        【解决方案5】:

        如果您的应用程序在 Windows 上运行,您可以通过 JNI 调用 CreateMutex

        jboolean ret = FALSE;    
        HANDLE hMutex = CreateMutex(NULL, FALSE, mutexName); 
        ret = TRUE;    
        if(WAIT_TIMEOUT == WaitForSingleObject(hMutex, 10))  
        {    
            ret = FALSE;  
        }
        else if(GetLastError() != 0)  
        {    
            ret = FALSE;  
        }
        

        如果没有其他人使用此互斥锁,则返回 true,否则返回 false。 如果您希望所有 Windows 会话共享您的互斥锁,您可以提供“myApplication”作为互斥锁名称或“Global\MyApplication”。

        编辑:它并不像看起来那么复杂 :) 我觉得它很干净。

        【讨论】:

        • 我也希望能够在 mac 和 linux 上使用,但这确实有助于 xD
        【解决方案6】:

        此代码的策略是保留上次运行时在注册表中的 PID,如果发现该 PID 在系统上运行,则不要启动。如果完成,请重置。

        首选项存储在 Windows 注册表中的HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

        import java.io.*;
        import java.util.prefs.Preferences;
        public class JavaApplication3 {
            public static void main(String[] args){
                if(isRunning()){
                    System.out.println("Two instances of this program cannot " +
                            "be running at the same time.  Exiting now");
                }
                else{
                    onStart();
                    epicHeavyWorkGoesHere();
                    onFinish();
                }
            }
            public static void epicHeavyWorkGoesHere(){
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException ex) {}
            }
            public static void onStart(){
                Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
                prefs.put("RUNNINGPID", getCurrentPID());
            }
            public static void onFinish(){
                Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
                prefs.put("RUNNINGPID", "");
            }
            public static boolean isRunning(){
                Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
        
                if (prefs.get("RUNNINGPID", null) == null || prefs.get("RUNNINGPID", null).equals(""))
                    return false;
        
                if (isProcessIdRunningOnWindows(Integer.parseInt(prefs.get("RUNNINGPID", null))))
                    return true;
                return false;
            }
            public static String getCurrentPID(){
                //This function should work with Windows, Linux and Mac but you'll have to 
                //test to make sure.  If not then get a suitable getCurrentPID function replacement.
                try{
                    java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
                    java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
                    jvm.setAccessible(true);
                    sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
                    java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
                    pid_method.setAccessible(true);
                    return pid_method.invoke(mgmt) + "";
                }
                catch(Exception e){
                    throw new RuntimeException("Cannot get the current PID");
                }
            }
            public static boolean isProcessIdRunningOnWindows(int pid){
                //This Function only works for windows, if you want it to work on linux
                //or mac, you will have to go find a replacement method that 
                //takes the processID as a parameter and spits out a true/false 
                //if it is running on the operating system.
                try {
                    Runtime runtime = Runtime.getRuntime();
                    String cmds[] = {"cmd", "/c", "tasklist /FI \"PID eq " + pid + "\""};
                    Process proc = runtime.exec(cmds);
        
                    InputStream inputstream = proc.getInputStream();
                    InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
                    BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
                    String line;
                    while ((line = bufferedreader.readLine()) != null) {
                        if (line.contains(" " + pid + " ")){
                            return true;
                        }
                    }
                    return false;
                }
                catch (Exception ex) {
                    throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
                }
            }
        }
        

        如果程序挂起并且 pid 保留在任务列表中,这将被阻止。您可以添加一个额外的注册表项来存储上次成功运行的时间,如果运行时间变得太大,存储的 PID 将被杀死,然后程序重新运行。

        【讨论】:

        • 1) 这将如何确定程序是否挂起?如果先前的程序挂起,注册表中的 PID 和任务列表中的进程仍然存在。您无法区分正在运行的进程或挂起的进程。 2)我可以扫描任务列表来查找进程(可能正在运行或挂起)。通过将 PID 保存在注册表中,您还能实现什么?
        • 啊,不错。我的意思是添加另一个存储上次成功完成时间的密钥。还要为“允许运行的最大时间量”保留一个变量。程序启动时,运行时间过长可以计算PID运行时间并杀死。
        【解决方案7】:

        与其他几个答案相反,最可靠的方法是在只有您知道的固定端口上创建一个ServerSocket,在油漆卡中。与任何锁定文件不同,它会在您的应用程序退出时自动释放,并且它之前通过BindException 存在是另一个实例已经在运行的非常可靠的信号。

        【讨论】:

          【解决方案8】:

          以下解决方案也适用于两个致命的场景。 1> 甚至您启动的 exe 在任务管理器中安排为 javaw.exe。 2> 您可以在两个位置安装您的应用程序,并且从两个位置启动它也可以工作。

          String tempDir = System.getProperty("java.io.tmpdir");// dependent to OS find any tem dir.
                  String filePath = tempDir + "lockReserverd.txt";
                  try {
                      final File file = new File(filePath);
          
                      if(file.exists())
                          return false;
          
                      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");
                  }
                  return false
          

          如果您的 application.exe 列在任务管理器中,这将起作用

          "tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
          

          【讨论】:

            【解决方案9】:

            这是一种使用用户主目录中自动命名的锁定文件的方法。 该名称基于运行 jar 的位置。

            import java.io.File;
            import java.io.FileNotFoundException;
            import java.io.IOException;
            import java.io.RandomAccessFile;
            import java.nio.channels.FileChannel;
            
            public class SingleInstance {
            
                @SuppressWarnings("resource")
                public static boolean isAlreadyRunning() {
                    File file;
                    FileChannel fileChannel;
                    File userDir = new File(System.getProperty("user.home"));
                    file = new File(userDir, myLockName());
            
                    if (!file.exists()) {
                        try {
                            file.createNewFile();
                            file.deleteOnExit();
                        } catch (IOException e) {
                            throw new RuntimeException("Unable to create Single Instance lock file!", e);
                        }
                    }
            
                    try {
                        fileChannel = new RandomAccessFile(file, "rw").getChannel();
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException("Single Instance lock file vanished!", e);
                    }
                    try {
                        if (fileChannel.tryLock() != null) {
                            return false;
                        }
                    } catch (Exception e) {
                    }
                    try {
                        fileChannel.close();
                    } catch (IOException e1) {
                    }
                    return true;
                }
            
                private static String myLockName() {
                    return "." + SingleInstance.class.getProtectionDomain().getCodeSource().getLocation().getPath()
                            .replaceAll("[^a-zA-Z0-9_]", "_");
                }
            }
            

            【讨论】:

              【解决方案10】:

              如果您的应用程序可以在任务管理器中安排一个唯一的名称,这也是一个很好的解决方案

               "tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
              

              【讨论】:

                【解决方案11】:

                检查 PID 和文件锁定技术

                我们可以将创建锁文件的进程的进程id写入文件中。当我们遇到一个现有的锁文件时,我们不只是退出,而是检查具有该 id 的进程是否还活着。如果没有,则创建一个新的应用程序实例。我认为 MongoDB 使用了这种技术。

                    static File file;
                    static FileChannel fileChannel;
                    static FileLock lock;
                    static boolean running = false;
                    static String currentPID = null;
                    static String lockFilePID = null;
                    public static final String USER_DIR = System.getProperty("user.dir");
                    public static final String LOCK_FILE = "az-client.lock";
                
                    public static boolean checkInstance() {
                        try {
                            file = new File(USER_DIR + File.separator + LOCK_FILE);
                            currentPID = Integer.toString(getCurrentPID());
                            if (!file.exists()) {
                                file.createNewFile();
                                writePID(currentPID);
                                lockFile();
                                addShudDownHook();
                                running = true;
                                return running;
                            } else {
                                if (isFileLocked()) {
                                    syso("App already running");
                                    System.exit(0);
                                } else {
                                    lockFilePID = getPIDFromLockFile();
                                    if (isProcessIdRunningOnWindows(Integer.parseInt(lockFilePID))) {
                                        lockFile();
                                        addShudDownHook();
                                        running = true;
                                        return running;
                                    } else {
                                        file.delete();
                                        file.createNewFile();
                                        writePID(currentPID);
                                        lockFile();
                                        addShudDownHook();
                                        running = true;
                                        return running;
                                    }
                                }
                            }
                        } catch (Exception e) {
                            syso(e + "App already running");
                            System.exit(0);
                        }
                        return running;
                    }
                
                    /**
                     * 
                     * @return
                     * @throws IOException
                     */
                    @SuppressWarnings("resource")
                    private static boolean isFileLocked() throws IOException {
                        fileChannel = new RandomAccessFile(file, "rw").getChannel();
                        lock = fileChannel.tryLock();
                        if (lock == null) {
                            fileChannel.close();
                            fileChannel = null;
                            return true;
                        } else {
                            lock.release();
                            fileChannel.close();
                            fileChannel = null;
                        }
                        return false;
                    }
                
                
                    public static int getCurrentPID() {
                        // This function should work with Windows, Linux and Mac but you'll have
                        // to
                        // test to make sure. If not then get a suitable getCurrentPID function
                        // replacement.
                        try {
                            java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
                            java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
                            jvm.setAccessible(true);
                            sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
                            java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
                            pid_method.setAccessible(true);
                            return (int) pid_method.invoke(mgmt);
                        } catch (Exception e) {
                            throw new RuntimeException("Cannot get the current PID");
                        }
                    }
                
                    public static boolean isProcessIdRunningOnWindows(int pid) {
                        // This Function only works for windows, if you want it to work on linux
                        // or mac, you will have to go find a replacement method that
                        // takes the processID as a parameter and spits out a true/false
                        // if it is running on the operating system.
                        try {
                            Runtime runtime = Runtime.getRuntime();
                            String cmds[] = { "cmd", "/c", "tasklist /FI \"PID eq " + pid + "\"" };
                            Process proc = runtime.exec(cmds);
                
                            InputStream inputstream = proc.getInputStream();
                            InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
                            BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
                            String line;
                            while ((line = bufferedreader.readLine()) != null) {
                                if (line.contains(" " + pid + " ")) {
                                    return true;
                                }
                            }
                            return false;
                        } catch (Exception ex) {
                            throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
                        }
                    }
                
                    /**
                     * This method write PID to Lock file
                     * 
                     * @param pid
                     * @throws Exception
                     */
                    private static void writePID(String pid) throws Exception {
                        try {
                            // To Do write PID to LockFile
                        } catch (Exception e) {
                            syso(e);
                            throw e;
                        }
                    }
                
                    /**
                     * This method return PID from Lock File
                     * 
                     * @return
                     * @throws Exception
                     */
                    private static String getPIDFromLockFile() throws Exception {
                        try {
                            return //To Do getPID from File
                        } catch (Exception e) {
                            syso(e);
                            throw e;
                        }
                    }
                
                    private static void addShudDownHook() {
                        try {
                            ShutdownHook shutdownHook = new ShutdownHook();
                            Runtime.getRuntime().addShutdownHook(shutdownHook);
                        } catch (Exception e) {
                            LogWriter.logger.error(e);
                        }
                    }
                
                    private static void unlockFile() {
                        try {
                            if (lock != null) {
                                lock.release();
                            }
                            fileChannel.close();
                            file.delete();
                            running = false;
                        } catch (IOException e) {
                            syso(e);
                        }
                    }
                
                    private static void lockFile() {
                        try {
                            fileChannel = new RandomAccessFile(file, "rw").getChannel();
                            lock = fileChannel.tryLock();
                            if (lock == null) {
                                fileChannel.close();
                                fileChannel = null;
                            }
                        } catch (IOException e) {
                            syso(e);
                        }
                    }
                
                    static class ShutdownHook extends Thread {
                        public void run() {
                            unlockFile();
                        }
                    }
                

                【讨论】:

                  猜你喜欢
                  • 2010-09-27
                  • 2010-09-28
                  • 2011-07-20
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多