如何确保只有一个 Java 应用程序实例正在运行?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10508846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 01:21:15  来源:igfitidea点击:

How to make sure that only a single instance of a Java application is running?

javamutex

提问by Epicblood

I want my application to check if another version of itself is already running.

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

For example, demo.jarstarted, user clicks to run it again, but the second instance realizes "oh wait, there is already a demo.jarrunning." and quits with a message.

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

采纳答案by Andrew White

What you are looking for can probably best be accomplished with a lock file. By lock file I simply mean a file that will have a predefined location and whose existence is your mutex.

您正在寻找的内容可能最好使用锁定文件来完成。我所说的锁定文件只是指一个具有预定义位置的文件,它的存在是您的互斥锁。

Test if that file exists when your program starts, if it does, exit immediately. Create a file in a known location. If your program exits normally, delete the lock file.

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

Probably best is if you can also populate the file with a pid (process id) so that you can detect abnormal exits that didn't delete the file but this get OS specific.

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

回答by Eric Leschinski

Enforce one instance of a program running with a ServerSocket Lock

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

Java Code. Put this into a file called Main.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){}
    }
  }
}

Compile and run it

编译并运行它

javac Main.java
java Main

Test it in a normal case:

正常情况下测试一下:

Run the program. You have 100 seconds to run the program again in another terminal, it will fall through saying its already running. Then wait 100 seconds, it should allow you to run it in the 2nd terminal.

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

Test it after force halting the program with a kill -9

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

  1. Start the program in terminal 1.
  2. kill -9 that process from another terminal within 100 seconds.
  3. Run the program again, it is allowed to run.
  1. 在终端 1 中启动程序。
  2. kill -9 在 100 秒内从另一个终端处理。
  3. 再次运行程序,就可以运行了。

Conclusion:

结论:

The socket occupation is cleaned up by the operating system when your program is no longer operating. So you can be sure that the program will not run twice.

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

Drawbacks

缺点

If some sneaky person, or some naughty process were to bind all of the ports, or just your port, then your program will not run because it thinks its already running.

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

回答by AZ_

Simple yet powerful tested solution.

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

    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();
        }
    }

Put these methods in some Util class and before launching your main class just check that if already exists then show some dialog to user otherwise launch an application. It works even if you abnormally shutdown java process or what ever you do. It is robust and efficient, no need to set up DataGram listeners or whatever...

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

回答by Eric Leschinski

The strategy of this code is to keep the PID around from the last run in the registry, if that PID is found running on the system, don't start. If you finish, reset.

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

The preferences are stored on Windows Registry in HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

首选项存储在 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");
        }
    }
}

If the program is hung and the pid remains in the task list this will be blocked. You could add an additional registry key that will store the last successful run time, and if the run time becomes too great, the stored PID is killed, and the program re-run.

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

回答by Edwin Buck

If you use a Mutex, logically that Mutex would need to be accessible from any JVM which was running a copy of "the program". In C programming, this might be accomplished via shared memory, but Java doesn't have such a thing by default.

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

With that understanding, there are plenty of ways to implement what you want. You could open a server socket on a designated port (the operating system assures that only one process is the recipient of the server socket, and subsequent opens fail).

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

You could use a "lock file" but it is a bit complicated, as the file you would need to use would really be a directory (and it becomes heavily dependent on whether directory creation is atomic for your file system, even though most directory creations are). If a sysadmin decides to run you via NFS, then things get even harder (if not impossible).

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

You can also do a number of nifty tricks with JVMs and debugging / JMI, provided you can somehow assure youself that all relevant JVMs are launched with the same configurations (in time, an impossible task).

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

Other people have used the exec facility to run the equivalent of a process listing, but it is a bit tricky due to the possibility of race condition (two processes simultaneously check, and fail to see each other).

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

In the end, the server socket route is probably the most stable, as it is guaranteed to only bind to one process by the TCP/IP stack (and is mediated by the operating system). That said, you will have to flush the socket of incoming messages, and it opens up the possibility of other security issues.

最后,服务器套接字路由可能是最稳定的,因为它保证仅通过 TCP/IP 堆栈绑定到一个进程(并由操作系统调解)。也就是说,您将不得不刷新传入消息的套接字,这可能会导致其他安全问题。

回答by Jerome

If your application is running on Windows, you can call CreateMutexthrough JNI.

如果您的应用程序在 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;  
}

This returns true if nobody else is using this mutex, false otherwise. You could provide "myApplication" as a mutex name or "Global\MyApplication" if you want your mutex to be shared by all Windows sessions.

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

Edit: It's not as complicated as it looks :) and I find it clean.

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

回答by Michael Conrad

Here is one method that uses an automatically named lock file in the user's home directory. The name is based on where the jar is being ran from.

这是一种使用用户主目录中自动命名的锁定文件的方法。该名称基于运行 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_]", "_");
    }
}

回答by user207421

Contrary to several other answers, the most reliable method is to create a ServerSocketon a fixed port known only to you, way up in the paint cards. It will automatically be released when your application exits, unlike any lock file, and its prior existence via a BindExceptionis a pretty infallible sign that another instance is already running.

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

回答by user207421

Following solution work in two deadly scenerio too. 1> Even your launched exe scheduled as javaw.exe in task manager. 2> You can install your application at two location and from launching both location it also works.

以下解决方案也适用于两个致命的场景。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

or

或者

This will work if your application.exe is listed in task manager

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

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

回答by user207421

This is also a good solution if your Appication can schedued in task manager with a unique name

如果您的 Appication 可以在具有唯一名称的任务管理器中进行调度,这也是一个很好的解决方案

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