如何确保只能执行一个java程序实例?

Exp*_*ing 13 java

需要在某个时间只能执行一个JAVA程序实例.我在不同的早期帖子中观察到了堆栈溢出中提出的大量解决方案.

解决方案基于:

  • 通过打开套接字:打开套接字连接.
  • 基于文件锁定:创建临时文件并保持锁定.并在JVM关闭时添加一个关闭钩子来解锁该文件.

我不想使用端口锁定,因为它可能导致端口使用可能发生冲突.

所以我在考虑使用文件锁定.在搜索了一下后,我发现基于端口锁定的机制的支持者提到,如果应用程序崩溃和其他IO错误,文件锁定可能是不可靠的.

我需要的是找到一个在跨平台和多个JDK中一致工作的解决方案.我的平台是Windows和Linux,JDK是Sun和IBM JDK.

任何人都可以对此有所了解吗?

小智 5

在unix系统中,通常的做法是创建一个文件,这是一个原子操作,然后检查文件是否可以创建。如果可以创建该文件,则该进程具有锁定并被允许运行。如果无法创建文件,则其他人必须拥有锁定,并且实例会立即终止。这种事情的代码

private boolean lock()
 {
   try
    {
        final File file=new File("bpmdj.lock");
        if (file.createNewFile())
        {
            file.deleteOnExit();
            return true;
        }
        return false;
    }
    catch (IOException e)
    {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

在应用程序的主要内容中,您然后开始

    if (!lock())
    {
        System.out.println("Cannot lock database. Check that no other instance of BpmDj is running and if so, delete the file bpmdj.lock");
        return;
    }
Run Code Online (Sandbox Code Playgroud)

当然,有两点需要注意。首先:如果应用程序崩溃,那么文件很可能不会被删除,从而给用户带来一些不便(他需要自己删除锁定文件)。

其次:java文档说明如下:

createNewFile原子地创建一个新的空文件...当且仅当具有此名称的文件尚不存在时。检查文件是否存在以及如果文件不存在则创建文件是单个操作,对于可能影响该文件的所有其他文件系统活动而言是原子操作。注意:此方法不应用于文件锁定,因为无法使生成的协议可靠地工作。应该改用 FileLock 工具。

特别是最后一个注释很有趣,因为在这种情况下,我们并没有真正将它用于文件锁定,只是为了检查是否存在同一应用程序的其他实例。然而,我有点好奇他们为什么写“结果协议不能可靠地工作”


Rah*_*thi 3

您可以使用 ManagementFactory 对象。从这里:-

import sun.management.ConnectorAddressLink;  
import sun.jvmstat.monitor.HostIdentifier;  

import sun.jvmstat.monitor.Monitor;  
import sun.jvmstat.monitor.MonitoredHost;  

import sun.jvmstat.monitor.MonitoredVm;  
import sun.jvmstat.monitor.MonitoredVmUtil;  
import sun.jvmstat.monitor.MonitorException;  
import sun.jvmstat.monitor.VmIdentifier;  

public static void main(String args[]) {  
/* The method ManagementFactory.getRuntimeMXBean() returns an identifier with applcation PID
   in the Sun JVM, but each jvm may have you own implementation. 
   So in anothers jvm, other than Sun, this code may not work., :( 
*/  
 RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();  
         final int runtimePid = Integer.parseInt(rt.getName().substring(0,rt.getName().indexOf("@")));  

  java.awt.EventQueue.invokeLater(new Runnable() {  
  public void run() {  

  // If exists another instance, show message and terminates the current instance.  
  // Otherwise starts application.  
  if (getMonitoredVMs(runtimePid))  
  {  
     new MainFrame().setVisible(true);  
  } else  
  JOptionPane.showMessageDialog(null,"There is another instance of this application running.");  

  }  
  });  
  }
Run Code Online (Sandbox Code Playgroud)

getMonitoredVMs (int processPid)方法接收当前应用程序 PID 作为参数,并捕获从命令行调用的应用程序名称,例如,应用程序是从 c:\java\app\test.jar 路径启动的,然后是值变量是“c:\java\app\teste.jar”。这样,我们将在下面代码的第 17 行仅捕获应用程序名称。之后,我们在JVM中搜索同名的另一个进程,如果找到并且应用程序PID不同,则说明这是第二个应用程序实例。

private static boolean getMonitoredVMs(int processPid) {  
         MonitoredHost host;  
         Set vms;  
try {  
     host = MonitoredHost.getMonitoredHost(new HostIdentifier((String)null));  
     vms = host.activeVms();  
    } catch (java.net.URISyntaxException sx) {  
 throw new InternalError(sx.getMessage());  
  } catch (MonitorException mx) {  
 throw new InternalError(mx.getMessage());  
 }  
 MonitoredVm mvm = null;  
 String processName = null;  
 try{  
     mvm = host.getMonitoredVm(new VmIdentifier(String.valueOf(processPid)));  
     processName = MonitoredVmUtil.commandLine(mvm);  
     processName = processName.substring(processName.lastIndexOf("\\") + 1,processName.length());  
             mvm.detach();  
     } catch (Exception ex) {  

     }  
 // This line is just to verify the process name. It can be removed. 
  JOptionPane.showMessageDialog(null,processName);  
  for (Object vmid: vms) {  
  if (vmid instanceof Integer) {  
  int pid = ((Integer) vmid).intValue();  
  String name = vmid.toString(); // default to pid if name not available  
  try {  
      mvm = host.getMonitoredVm(new VmIdentifier(name));  
      // use the command line as the display name  
    name =  MonitoredVmUtil.commandLine(mvm);  
    name = name.substring(name.lastIndexOf("\\")+1,name.length());  
    mvm.detach();  
    if ((name.equalsIgnoreCase(processName)) && (processPid != pid))  
    return false;  
   } catch (Exception x) {  
   // ignore  
   }  
   }  
   }  

   return true;  
   }
Run Code Online (Sandbox Code Playgroud)

另请检查使用 SingleInstanceService 服务

javax.jnlp.SingleInstanceService为应用程序提供了一组方法将其自身注册为单例,并注册侦听器以处理从应用程序的不同实例传入的参数。

  • 我可以看到此解决方案有两个问题: (a) 这依赖于 SUN JDK,可能不适用于 IBM J9 JDK。(b) 它需要tools.jar,这会增加应用程序的大小。 (2认同)