如何确保只运行Java应用程序的单个实例?

时间:2012-05-09 01:46:57

标签: java mutex

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

例如,demo.jar已启动,用户点击再次运行它,但第二个实例意识到“哦等等,已经有demo.jar正在运行。”并退出并留言。

12 个答案:

答案 0 :(得分:9)

强制执行使用ServerSocket Lock

运行的程序的一个实例

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. 再次运行程序,允许它运行。
  4. <强>结论:

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

    <强>缺点

    如果一些偷偷摸摸的人,或者一些顽皮的进程要绑定所有端口,或只是你的端口,那么你的程序将无法运行,因为它认为它已经在运行。

答案 1 :(得分:5)

您正在寻找的内容可能最好通过锁定文件来完成。通过锁定文件,我只是指一个具有预定义位置并且其存在是您的互斥锁的文件。

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

如果您还可以使用pid(进程ID)填充文件,那么最好的情况是,这样您就可以检测到没有删除文件的异常退出,但这会导致操作系统特定。

答案 2 :(得分:5)

  

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

    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监听器或其他......

答案 3 :(得分:2)

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

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

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

你也可以使用JVM和调试/ JMI做一些漂亮的技巧,只要你能以某种方式确保所有相关的JVM都以相同的配置启动(及时,一个不可能完成的任务)。

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

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

答案 4 :(得分:2)

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

编辑:它没有看起来那么复杂:)我发现它很干净。

答案 5 :(得分:2)

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

首选项存储在HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

中的Windows注册表中
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将被终止,程序将重新运行。

答案 6 :(得分:1)

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

```

答案 7 :(得分:0)

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

答案 8 :(得分:0)

以下解决方案也在两个致命的场景中工作。 1 GT;甚至你的已启动的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 :(得分:0)

如果您的应用程序可以在任务管理器中使用唯一名称

进行调度,这也是一个很好的解决方案
 "tasklist /FI \"IMAGENAME eq "+MyApplication+".exe

答案 10 :(得分:0)

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

答案 11 :(得分:-1)

FileLock在Linux上不起作用。请不要使用FileLock。我认为获取Process的名称(如果可能的话,通过创建一个唯一的名称)将是解决此问题的一种方法。我认为进程ID是自动分配的。

获取流程时请参阅:

  

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