简体   繁体   English

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

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

I want my application to check if another version of itself is already running.我希望我的应用程序检查自己的另一个版本是否已经在运行。

For example, demo.jar started, user clicks to run it again, but the second instance realizes "oh wait, there is already a demo.jar running."比如demo.jar启动了,用户点击再次运行,但是第二个实例实现“哦等等,已经有demo.jar运行了”。 and quits with a message.并退出一条消息。

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.您有 100 秒的时间在另一个终端中再次运行该程序,它会说它已经在运行。 Then wait 100 seconds, it should allow you to run it in the 2nd terminal.然后等待 100 秒,它应该允许您在第二个终端中运行它。

Test it after force halting the program with a kill -9在用 kill -9 强制停止程序后测试它

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

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

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)填充文件,以便您可以检测未删除文件的异常退出,但这是特定于操作系统的。

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.将这些方法放在一些 Util 类中,然后在启动主类之前检查是否已经存在,然后向用户显示一些对话框,否则启动应用程序。 It works even if you abnormally shutdown java process or what ever you do.即使您异常关闭 java 进程或您所做的任何事情,它也能工作。 It is robust and efficient, no need to set up DataGram listeners or whatever...它健壮且高效,无需设置 DataGram 侦听器或其他任何...

If you use a Mutex, logically that Mutex would need to be accessible from any JVM which was running a copy of "the program".如果您使用互斥锁,从逻辑上讲,需要从任何运行“程序”副本的 JVM 访问互斥锁。 In C programming, this might be accomplished via shared memory, but Java doesn't have such a thing by default.在 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).最后,服务器套接字路由可能是最稳定的,因为它保证仅通过 TCP/IP 堆栈绑定到一个进程(并由操作系统调解)。 That said, you will have to flush the socket of incoming messages, and it opens up the possibility of other security issues.也就是说,您将不得不刷新传入消息的套接字,这可能会导致其他安全问题。

If your application is running on Windows, you can call CreateMutex through 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.如果没有其他人使用此互斥锁,则返回 true,否则返回 false。 You could provide "myApplication" as a mutex name or "Global\\MyApplication" if you want your mutex to be shared by all Windows sessions.如果您希望您的互斥锁被所有 Windows 会话共享,您可以提供“myApplication”作为互斥锁名称或“Global\\MyApplication”。

Edit: It's not as complicated as it looks :) and I find it clean.编辑:它并不像看起来那么复杂:)而且我觉得它很干净。

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.此代码的策略是将 PID 保留在注册表中上次运行后的位置,如果发现该 PID 在系统上运行,则不要启动。 If you finish, reset.如果完成,请重置。

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.如果程序挂起并且 pid 保留在任务列表中,这将被阻止。 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 将被终止,然后程序重新运行。

Contrary to several other answers, the most reliable method is to create a ServerSocket on a fixed port known only to you, way up in the paint cards.与其他几个答案相反,最可靠的方法是在只有您知道的固定端口上创建一个ServerSocket ,在油漆卡中向上。 It will automatically be released when your application exits, unlike any lock file, and its prior existence via a BindException is a pretty infallible sign that another instance is already running.与任何锁定文件不同,它会在您的应用程序退出时自动释放,并且它通过BindException先前存在是另一个实例已经在运行的非常可靠的标志。

Following solution work in two deadly scenerio too.以下解决方案也适用于两个致命的场景。 1> Even your launched exe scheduled as javaw.exe in task manager. 1> 甚至你启动的 exe 在任务管理器中被安排为 javaw.exe。 2> You can install your application at two location and from launching both location it also works. 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

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_]", "_");
    }
}

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

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

Check PID and file lock technique检查PID和文件锁定技术

We can write the process id of the process that created the lock file into the file.我们可以将创建锁文件的进程的进程id写入文件中。 When we encounter an existing lock file, we do not just quit, but we check if the process with that id is still alive.当我们遇到一个现有的锁文件时,我们不只是退出,而是检查具有该 id 的进程是否还活着。 If not, then create a new application instance.如果没有,则创建一个新的应用程序实例。 I think MongoDB use this technique.我认为 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();
        }
    }

FileLock does not work on Linux. FileLock在Linux上不起作用。 Please don't use FileLock. 请不要使用FileLock。 I think getting the Process's Name (by creating a unique name if possible) would be a way to tackle this problem. 我认为获取进程的名称(如果可能,通过创建一个唯一的名称)将是解决此问题的一种方法。 I think Process ID is auto-assigned. 我认为进程ID是自动分配的。

Refer to this on getting the Process: 获取流程请参阅此处:

http://www.itechp2pexchange.com/content/linux-unix-run-only-one-instance-script http://www.itechp2pexchange.com/content/linux-unix-run-only-one-instance-script

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM