15

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

解决方案基于:

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

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

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

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

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

4

5 回答 5

6

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

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

其次:java文档说明如下:

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

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

于 2014-12-04T14:54:08.480 回答
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.");  

  }  
  });  
  }

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

还要检查使用 SingleInstanceService 服务

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

于 2013-09-29T19:29:10.073 回答
1

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

您可以在此stackoverflow 答案中获取本地套接字的示例。

可以在此处找到 Windows 中命名管道的示例

于 2013-09-29T19:45:35.840 回答
1

嗨,有很多方法可以做到这一点,只需访问此页面。我只是复制粘贴。也查看这个线程 [stackoverflow][1]

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

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

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

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

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

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

  1. 通过从本地文件系统访问文件。这也是做同样事情的另一种方法。但这并不是那么可取,因为有时当 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);
     }
   }
}
  1. 通过使用来自 tools.jar 的 java sun.jvmstat 包。

只需查看代码链接

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

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

于 2013-09-29T19:54:45.253 回答
0

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

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

另请参阅如何实现单实例 Java 应用程序?

于 2016-11-23T11:38:05.333 回答