仅在尚未运行的情况下启动Java程序

时间:2009-03-18 21:32:24

标签: java mutual-exclusion

我需要在Java应用程序中启动1-3个具有用户定义路径的外部程序。我的要求很少:

  1. 我不希望程序在已经运行的情况下执行

  2. 我不希望任何程序从我的Java应用程序中窃取焦点

  3. 我不在乎他们中的任何一个是否无法启动。他们只需要默默地失败。

  4. 这是我到目前为止所提出的:

    ProcessBuilder pb = new ProcessBuilder(userDefinedPath1);
    try {
        pb.start();
    }
    catch (Exception e) {
        // Something went wrong, just ignore
    }
    

    然后我用另外两条路径再重复3次。这开始就像我期望的那样,并且满足我的第三个要求就好了,但是前两个失败了。

    这样做的最佳方式是什么?

    编辑:

    1. 我对这些其他应用程序没有任何控制权。他们是第三方。此外,用户可以随时手动启动或停止它们。

    2. 我知道可执行文件的确切名称(例如“blah.exe”),它们始终是相同的,但是可执行文件的路径不一定是。

    3. 批处理文件包装器在这里不可行。

    4. 其他应用程序不是 java应用程序,只是普通的旧Windows可执行文件。

9 个答案:

答案 0 :(得分:6)

我猜你无法控制其他两个应用程序...如果你这样做,这不会太糟糕 - 你可以让他们听一个套接字,看看套接字是否可用当你上来的时候。

下一个解决方案实际上可能与语言无关。您可以通过批处理文件包装器管理整个系统。编写一个批处理文件,在启动时创建一个文件,并在文件停止时删除它。 Unix系统使用这种技术很多 - 他们经常将文件称为锁定文件。

如果只有你的应用程序会启动这些其他应用程序,那么你可以简单地跟踪你是否已经启动它,所以我猜这是不可能的,或者你不会问,所以我是假设用户可能已经通过其他机制启动了这些程序。

如果您无法控制其他应用程序的启动,甚至无法编写批处理文件来启动它们,那么您就无法做您想做的事情(注意,应用程序必须始终使用批处理文件,即使用户手动启动它们。)

我只是一个最后的努力可能是获得进程状态并解析它,但你必须确切地知道PS中其他应用程序的调用,这不是真正的微不足道。此外,所有Java应用程序在大多数进程状态打印输出中往往具有相同的确切签名,这可能会使这无用。

问题在于,如果其中一个程序是在您的应用程序之外启动的,那么您几乎没有办法确定这一事实,除非您碰巧知道它的确切过程状态签名,即使这样也是不稳定的。

答案 1 :(得分:6)

我提供两个答案,一个用于Linux:

如果程序已在运行,请不要运行该程序,将其放在名为Main.java的文件中

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

class JustOneLock {
  FileLock lock;
  FileChannel channel;

  public boolean isAppActive() throws Exception{
    File file = new File(System.getProperty("user.home"),
            "FireZeMissiles1111" + ".tmp");
    channel = new RandomAccessFile(file, "rw").getChannel();

    lock = channel.tryLock();
    if (lock == null) {
      return true;
    }
    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        try {
          lock.release();
          channel.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
    return false;
  }
}

public class Main {
  public static void main(String[] args)throws Exception {
    JustOneLock u = new JustOneLock();

    if (u.isAppActive()) {
      System.out.println("Already active, stop!");
      System.exit(1);
    }
    else {
      System.out.println("NOT active... Do hard work for 5 seconds.");
      try{Thread.sleep(5000);}catch(Exception e){}
    }
  }
}

编译并运行它。然后打开一个新的终端,并尝试再次运行它,而另一个正在运行,它不会。

Windows的另一个答案

如果该程序已在当前系统上运行,则不允许自行运行。这适用于仅限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(5000);
        } 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 is designed to get the PID from the windows system, it may
        //not work for Linux or Mac.  You'll have to acquire a suitable getCurrentPID function
        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
        //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 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在系统上运行,则不要启动。如果完成,请重置。

首选项存储在HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

中的Windows注册表中

我建议不要使用文件锁定来确保java应用程序不会同时运行两次,因为如果程序崩溃或永久挂起并被杀死,那么锁定会处于不一致状态,甚至可能存在重启将导致问题,因为程序如何知道仍在运行的程序与已崩溃的程序之间的区别并将锁定的文件锁定?

答案 2 :(得分:3)

这就是我解决问题的方法,它是使用纯java的简单解决方案:

注意,如果以下代码崩溃一次,则会处于不一致状态,无法处理挂起或崩溃的程序。

public void main(String[] args){
    if(isRunning()){
        JOptionPane.showMessageDialog(this, "2 instances of this program cannot be running at the same time. \n Exiting now");
        System.exit(0);
    }else{
        onStart();
    }
}    

public final void onStart(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    prefs.put("RUNNING", "true");
}

public final void onFinish(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    prefs.put("RUNNING", "false");
}

public boolean isRunning(){
    Preferences prefs;
    prefs = Preferences.userRoot().node(this.getClass().getName());
    return prefs.get("RUNNING", null) != null ? Boolean.valueOf(prefs.get("RUNNING", null)) : false;
}

private void formWindowClosing(java.awt.event.WindowEvent evt) {
    onFinish();
}

答案 3 :(得分:2)

我非常有信心这是最好的方式;应用程序在启动时运行“服务器”,如果服务器的端口已被占用,则应用程序将关闭。

public static ServerSocket ss;

public static void main (String[] args) {

    ss = null;

    try {
        ss = new ServerSocket(1044);
    } catch (IOException e) {
        System.err.println("Application already running!");
        System.exit(-1);
    }
}

答案 4 :(得分:1)

如果我在基于unix的系统上处理这个问题,我很想写一个本机函数来收集进程信息并尝试启动外部应用程序。这违背了Java的精神,但您尝试收集的信息类型和应用程序启动的控制都在JVM之外。

我对Windows编程的了解不足以指出正确的方向,但我认为您可以使用.NET语言或纯C ++访问Windows API调用,这可以帮助您实现第二个标准。也许您可以更改问题以吸引可以提供帮助的非Java开发人员。


编辑:

查看Windows API的Shell Execute Function。 SW_SHOWNOACTIVATE选项似乎允许当前窗口保持活动状态。

答案 5 :(得分:1)

请参阅Check if a program or process is running (Windows)

此提示将检测给定程序是否正在运行。好吧,由于vbscript的缘故,它并不漂亮,但它很容易实现。

答案 6 :(得分:1)

您可以对所有系统执行一项小操作,这包括文件锁定功能。

在程序运行中 首先,创建一个文件并将其锁定。将其命名为lockfile。生成时间戳并加密它。把它写进去。

在相同的程序运行中 首先,检查是否存在锁定文件。试图锁定它。忽略此文件被锁定的可能性。阅读并检查时间戳。如果时间戳非常小(如1小时前),请尝试运行该程序。您将错误地写回时间戳,因为它已被锁定。在这种情况下,假设已经打开了一个程序。

在程序退出时,删除此锁定文件。

答案 7 :(得分:0)

在windows xp(pro?)上,你可以启动命令'tasklist'并解析输出以确定进程是否正在运行。 你可以使用线程来避免任何焦点问题(我认为)

答案 8 :(得分:0)

您可以使用jps来了解正在运行的java程序状态。 jps是java工具之一,如javac,java等,