如何确保Java应用程序只运行一个实例?
我希望我的应用程序检查自己的另一个版本是否已经在运行如何确保Java应用程序只运行一个实例?,java,mutex,Java,Mutex,我希望我的应用程序检查自己的另一个版本是否已经在运行 例如,demo.jar启动,用户点击再次运行,但第二个实例实现了“哦,等等,已经有一个demo.jar正在运行。”并通过一条消息退出。您正在寻找的东西可能最好用一个锁文件来完成。我所说的锁定文件只是指一个具有预定义位置的文件,它的存在就是您的互斥锁 测试程序启动时该文件是否存在,如果存在,请立即退出。在已知位置创建文件。如果程序正常退出,请删除锁定文件 最好的方法可能是,您还可以使用pid(进程id)填充该文件,这样您就可以检测到没有删除该文
例如,
demo.jar
启动,用户点击再次运行,但第二个实例实现了“哦,等等,已经有一个demo.jar
正在运行。”并通过一条消息退出。您正在寻找的东西可能最好用一个锁文件来完成。我所说的锁定文件只是指一个具有预定义位置的文件,它的存在就是您的互斥锁
测试程序启动时该文件是否存在,如果存在,请立即退出。在已知位置创建文件。如果程序正常退出,请删除锁定文件
最好的方法可能是,您还可以使用pid(进程id)填充该文件,这样您就可以检测到没有删除该文件但得到特定于操作系统的异常出口 如果使用互斥体,从逻辑上讲,互斥体需要可以从运行“程序”副本的任何JVM访问。在C编程中,这可以通过共享内存来实现,但Java在默认情况下没有这样的功能 有了这种理解,就有很多方法来实现你想要的。您可以在指定端口上打开服务器套接字(操作系统确保只有一个进程是服务器套接字的接收者,后续打开失败) 您可以使用“锁定文件”,但它有点复杂,因为您需要使用的文件实际上是一个目录(并且它在很大程度上取决于目录创建对于您的文件系统是否是原子的,即使大多数目录创建都是原子的)。如果系统管理员决定通过NFS运行您,那么事情会变得更加困难(如果不是不可能的话) 您还可以使用jvm和调试/JMI执行许多漂亮的技巧,前提是您可以以某种方式确保所有相关jvm都是使用相同的配置启动的(在时间上,这是一项不可能完成的任务) 其他人使用exec工具来运行与进程列表相当的程序,但由于可能存在争用条件(两个进程同时检查,并且无法看到对方),这有点棘手
最后,服务器套接字路由可能是最稳定的,因为TCP/IP堆栈保证它只绑定到一个进程(并且由操作系统中介)。这就是说,您将必须刷新传入消息的套接字,这将打开其他安全问题的可能性。如果您的应用程序在Windows上运行,您可以通过JNI调用
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在系统上运行,不要启动。如果完成,请重置 首选项存储在Windows注册表中的
HKEY\U LOCAL\U 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将被终止,程序将重新运行
简单但功能强大的测试解决方案
将这些方法放在某个Util类中,在启动主类之前,只需检查是否已经存在,然后向用户显示一些对话框,否则启动应用程序。即使您异常地关闭java进程或您所做的任何事情,它也可以工作。它强大而高效,无需设置数据报侦听器或其他什么…与其他几个答案相反,最可靠的方法是在一个只有您知道的固定端口上创建一个
服务器套接字
,一直在绘制卡中。与任何锁文件不同,当您的应用程序退出时,它将自动释放,并且它先前通过BindException
存在是另一个实例已在运行的可靠标志。强制使用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> 甚至在任务管理器中,已启动的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
如果您的应用程序可以在任务管理器中使用唯一的名称进行调度,那么这也是一个很好的解决方案
"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
检查PID和文件锁定技术 我们可以将创建锁文件的进程的进程id写入该文件。当我们遇到一个现有的锁文件时,我们不仅要退出,还要检查具有该id的进程是否仍然处于活动状态。如果没有,则在中创建一个新的应用程序
"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
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();
}
}
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_]", "_");
}
}