如何保证一个java程序只能执行一个实例? [复制]

Posted

技术标签:

【中文标题】如何保证一个java程序只能执行一个实例? [复制]【英文标题】:How to ensure only one instance of a java program can be executed? [duplicate] 【发布时间】:2013-09-29 19:24:12 【问题描述】:

要求一个JAVA程序在某一时刻只能执行一个实例。我在早期的不同帖子中观察到了很多关于堆栈溢出的解决方案。

解决方案基于:

通过打开套接字:打开套接字连接。 基于文件锁定:创建临时文件并持有锁定。并添加一个关闭挂钩以在 JVM 关闭时解锁该文件。

我不想使用端口锁定,因为它可能会导致端口使用冲突。

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

我需要的是找到一个可以在跨平台和多个 JDK 中始终如一地工作的解决方案。我的目标平台是 Windows 和 Linux,JDK 是 Sun 和 IBM JDK。

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

【问题讨论】:

一种改进文件锁定方法的方法是将进程的PID放入锁定文件中。这样,如果一个新实例启动,它可以检查创建锁文件的进程是否还活着。如果没有,锁定文件只是应用程序崩溃或类似事件后的剩余部分,可以忽略。这可能需要一些不可移植的代码,尽管进程 ID 是特定于操作系统的。 获取进程 ID 将导致生成特定于操作系统的代码,因此这不是我想要的解决方案。 添加一个ShutdownHook 会释放文件锁怎么样? 这能回答你的问题吗? How to implement a single instance Java application? 【参考方案1】:

在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;
    

然后在应用程序的主目录中开始

    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;
    

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

其次:java documentation 声明如下:

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

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

【讨论】:

注意deleteOnExit() 一般不应该使用(重复使用会造成内存溢出)但这是完美的用例。【参考方案2】:

您可以使用 ManagementFactory 对象。来自here:-

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.");  

    
  );  
  

getMonitoredVMs(int processPid) 方法接收参数 当前应用程序 PID,并捕获被调用的应用程序名称 例如,从命令行启动应用程序 c:\java\app\test.jar 路径,那么value变量为 “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;  
   

同时检查 Using the SingleInstanceService Service

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

【讨论】:

我可以看到此解决方案存在两个问题: (a) 这取决于 SUN JDK,可能不适用于 IBM J9 JDK。 (b) 它需要 tools.jar 来增加应用程序的大小。 对于 (b) The problem of all this code are the imports, that are defined only in the file tools.jar file and has a size of 11MB. But to solve this problem, I have unpacked this file and a packed a new file called VM.ZIP only with the necessary classes, and it has a size of just 81kb. 对于(a):-在您的问题中,您提到您的预期平台是 SUN JDK。我不确定 IBM J9 JDK 是否也可以在那里工作。至少我会说最好试一试! :) 谢谢拉胡尔。请注意,我的预期平台是 Windows 和 Linux,JDK 是 Sun 和 IBM JDK。 JDK版本> 1.4【参考方案3】:

如果您熟悉 C,则可以使用 windows 中的命名管道和 unix 中的本地套接字来解决此问题。它们都需要一点 JNI。 这些通信通道是您应用程序的资源,因此当您的应用程序崩溃时,操作系统有责任释放您的资源。此外,它们由文本名称标识,因此名称冲突的可能性与文件锁定相同。

您可以在*** answer 中获取本地套接字的示例。

可以在here找到一个windows命名管道的例子

【讨论】:

【参考方案4】:

您好,有很多方法可以做到这一点,只需访问此页面即可。 我只是复制粘贴。 还可以查看此线程 [堆栈溢出][1]

Java 世界中问得最多的问题之一是如何制作 Java 应用程序作为单个实例。

我在谷歌上搜索并找到了许多技术。 我在这里发布了一些流行的技术。 有问题就直接联系吧......

    通过捕获端口或通过 ServerSocket(短代码)。 在这个方法中,我们创建了一个 java.net.ServerSocket 类的对象。 通过传递一个端口号,我们在第一个实例时被捕获,所以 如果发生另一个实例,它会抛出一个绑定异常并且 您可以跟踪系统上是否有更多实例正在运行。

只需查看代码链接 http://yuvadevelopers.dmon.com/java_examples/Single_Instance_small.htm

    通过捕获端口或通过 ServerSocket(大代码)。 它与第一种方法相同,但在谷歌时我得到了这个大代码 不同的选项只需通过代码。

只需查看代码链接 请参阅此处的原始来源从 google 获取 http://www.rbgrn.net/blog/2008/05/java-single-application-instance.html

    通过从本地文件系统访问文件。 这也是做同样事情的另一种方法。 但这并不是那么可取,因为有时当 JVM 崩溃或由于某些 IO 错误发生然后文件没有被删除 从硬盘。 注意:- 不要将您的文件(您可以使用任何文件)放在 C 盘或存在操作系统的位置。 代码见下文
/* * JAVA单实例设置程序 * 版权所有 2009 @ yuvadeveloper * 代码作者:- Prashant Chandrakar * */ 导入 java.net.ServerSocket; 导入 javax.swing.JOptionPane; 导入 javax.swing.JFrame; 导入 java.io.IOException; 导入 java.net.BindException; 类 SingleInstance 公共静态服务器套接字服务器套接字; 公共静态字符串错误类型=“访问错误”; public static String error = "应用程序已经在运行....."; 公共静态无效主(字符串作为[]) 尝试 //创建服务器套接字对象并绑定到某个端口号 serverSocket = new ServerSocket(15486); ////不要放80等常用端口号 ////因为它们已经被系统使用了 JFrame jf = new JFrame(); jf.setVisible(true); jf.setSize(200, 200); 捕获(BindException 除外) JOptionPane.showMessageDialog(null, error, errortype, JOptionPane.ERROR_MESSAGE); System.exit(0); 捕获(IOException 除外) JOptionPane.showMessageDialog(null, error, errortype, JOptionPane.ERROR_MESSAGE); System.exit(0);
    通过使用来自 tools.jar 的 java sun.jvmstat 包。

代码见链接

    通过使用 Launch4j 应用程序。 它是为您的应用程序创建 EXE 的第三方工具。 它为您提供了创建单实例应用程序的便利。 就试一试吧。它是完美的工具。

只需查看 launch4j 应用程序文档 http://launch4j.sourceforge.net/

【讨论】:

您好 jdev,我已经研究了您提到的链接,但仍然没有找到可以处理所有情况的跨平台和跨 JVM 解决方案。 通过使用文件锁或套接字,您可以实现跨平台解决方案。文件锁有什么问题。如果您的程序有另一个共享资源,如 DB,您可以通过插入一些值来启动应用程序数据库,然后检查它。你需要一些东西来记录你的应用程序启动。它可以是数据库、文件或套接字。 基于套接字的解决方案不是一个好主意,因为它可能会导致端口冲突。对于我的具体情况,我没有任何数据库。所以看起来唯一的选择是使用文件锁。然而,已经看到基于端口锁定机制的支持者提到,如果应用程序崩溃和其他 IO 错误,文件锁定可能不可靠,因此提出了问题。 程序崩溃时会自动删除。那么就没有问题了。【参考方案5】:

您可以使用 JUnique 库。它提供对运行单实例 java 应用程序的支持,并且是开源的。它基于文件锁,但也使用随机端口来发送/接收来自其他正在运行的 java 实例的消息。

http://www.sauronsoftware.it/projects/junique/

在How to implement a single instance Java application?查看我的完整答案

【讨论】:

以上是关于如何保证一个java程序只能执行一个实例? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Java。如何将一个LinkedList里的元素全部复制到另一LinkedList容器里?

java 核心学习笔记 单例类

java线程如何一分钟执行一次

聊聊“单例模式”的多种实现方式。

java 程序中怎么保证多线程的运行安全?

Java的单例模式