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

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

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

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

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

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

实施使用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. 再次运行程序,允许运行。

结论:

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

缺点

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

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

  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类中,在启动主类之前,只需检查是否已存在,然后向用户显示一些对话框,否则启动应用程序。 即使你exception关闭java进程或者你做了什么,它仍然有效。 它强大而高效,无需设置DataGram监听器或其他任何东西……

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

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

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

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

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

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

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

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

此代码的策略是保持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将被终止,程序将重新运行。

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

“`

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

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

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

以下解决方案也在两个致命的场景中工作。 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 

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

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

获取流程请参阅此处:

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