web-dev-qa-db-ja.com

Javaアプリケーションの複数のインスタンスを起動しないようにする

ユーザーが自分のJavaアプリケーションを複数回並行して実行しないようにしたい。

これを防ぐために、アプリケーションを開くときにロックファイルを作成し、アプリケーションを閉じるときにロックファイルを削除しました。

アプリケーションの実行中は、jarの別のインスタンスを開くことはできません。ただし、タスクマネージャを使用してアプリケーションを強制終了した場合、アプリケーションのウィンドウを閉じるイベントはトリガーされず、ロックファイルは削除されません。

ロックファイル方式が機能することを確認するにはどうすればよいですか、または他にどのようなメカニズムを使用できますか?

22
Sathish

同様の議論は http://www.daniweb.com/software-development/Java/threads/83331 にあります。

ServerSocketをバインドします。バインドに失敗した場合は、起動を中止します。 ServerSocketは1回しかバインドできないため、プログラムの1つのインスタンスのみを実行できます。

そして、あなたが尋ねる前に、いいえ。 ServerSocketをバインドするからといって、ネットワークトラフィックに対してオープンであるとは限りません。これは、プログラムがaccept()を使用してポートの「リッスン」を開始した場合にのみ有効になります。

12
Sap

私はあなたが試すことができる2つのオプションを見ます:

  1. Java シャットダウンフック
  2. ロックファイルにメインプロセス番号を保持させます。別のインスタンスを実行するときに、プロセスが存在する必要があります。システムに見つからない場合は、ロックを解除して上書きできると考えられます。
7
sgibly

FileLockを使用できます。これは、複数のユーザーがポートを共有する環境でも機能します。

String userHome = System.getProperty("user.home");
File file = new File(userHome, "my.lock");
try {
    FileChannel fc = FileChannel.open(file.toPath(),
            StandardOpenOption.CREATE,
            StandardOpenOption.WRITE);
    FileLock lock = fc.tryLock();
    if (lock == null) {
        System.out.println("another instance is running");
    }
} catch (IOException e) {
    throw new Error(e);
}

ガベージコレクションも存続します。プロセスが終了するとロックが解除されます。通常の終了やクラッシュなどは関係ありません。

5
Manuel

アプリケーションの起動時に ServerSocket インスタンスを使用して特定のポートにバインドするサーバーソケットを作成するのは簡単な方法です。
ServerSocket.accept()はブロックするため、独自のスレッドで実行することは、メインのThreadをブロックしないことを意味します。

検出されたときに例外がスローされた例を次に示します。

public static void main(String[] args) {       
    assertNoOtherInstanceRunning();
    ...     // application code then        
}

public static void assertNoOtherInstanceRunning() {       
    new Thread(() -> {
        try {
            new ServerSocket(9000).accept();
        } catch (IOException e) {
          throw new RuntimeException("the application is probably already started", e);
        }
    }).start();       
}
4
davidxxx

ロックファイルを作成したプロセスのプロセスIDをファイルに書き込むことができます。既存のロックファイルに遭遇した場合、単に終了するだけでなく、そのIDを持つプロセスがまだ生きているかどうかを確認します。そうでない場合は、先に進むことができます。

4
Thilo

次のようなサーバーソケットを作成できます

       new ServerSocket(65535, 1, InetAddress.getLocalHost());

コードの最初に。次に、AddressAlreadyInUse例外がメインブロックでキャッチされた場合、適切なメッセージを表示できます。

2

..他にどのようなメカニズムを使用できますか?

アプリの場合。 Java Web Start を使用して起動できるGUIがあります。 [〜#〜] jnlp [〜#〜] Web-Startに提供されるAPIは、SingleInstanceServiceを提供します。これが私の SingleInstanceServiceのデモ です。

1
Andrew Thompson

あなたはこのようなものを書くことができます。

ファイルが存在する場合は、削除してみてください。削除できない場合。アプリケーションはすでに実行されていると言えます。

ここで、同じファイルを再度作成し、sysoutとsyserrをリダイレクトします。

これは私のために働きます

1
Karthej Reddy

私はしばらくの間、この同じ問題に苦しんでいました...ここに提示されたアイデアはどれも私にはうまくいきませんでした。すべての場合において、ロック(ファイル、ソケットなど)は2番目のプロセスインスタンスに保持されなかったため、2番目のインスタンスは引き続き実行されました。

そこで、最初のプロセスのプロセスIDを使用して.pidファイルを単純に作成するという古い方法を試すことにしました。次に、2番目のプロセスが.pidファイルを見つけると終了し、ファイルで指定されたプロセス番号がまだ実行中であることが確認されます。このアプローチは私のために働いた。

かなりのコードがありますが、ここでは完全に使用できるように提供しています...完全なソリューションです。

package common.environment;

import org.Apache.logging.log4j.LogManager;
import org.Apache.logging.log4j.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import Java.io.*;
import Java.nio.charset.Charset;

public class SingleAppInstance
{
    private static final @Nonnull Logger log = LogManager.getLogger(SingleAppInstance.class.getName());

    /**
     * Enforces that only a single instance of the given component is running. This
     * is resilient to crashes, unexpected reboots and other forceful termination
     * scenarios.
     *
     * @param componentName = Name of this component, for disambiguation with other
     *   components that may run simultaneously with this one.
     * @return = true if the program is the only instance and is allowed to run.
     */
    public static boolean isOnlyInstanceOf(@Nonnull String componentName)
    {
        boolean result = false;

        // Make sure the directory exists
        String dirPath = getHomePath();
        try
        {
            FileUtil.createDirectories(dirPath);
        }
        catch (IOException e)
        {
            throw new RuntimeException(String.format("Unable to create directory: [%s]", dirPath));
        }

        File pidFile = new File(dirPath, componentName + ".pid");

        // Try to read a prior, existing pid from the pid file. Returns null if the file doesn't exist.
        String oldPid = FileUtil.readFile(pidFile);

        // See if such a process is running.
        if (oldPid != null && ProcessChecker.isStillAllive(oldPid))
        {
            log.error(String.format("An instance of %s is already running", componentName));
        }
        // If that process isn't running, create a new lock file for the current process.
        else
        {
            // Write current pid to the file.
            long thisPid = ProcessHandle.current().pid();
            FileUtil.createFile(pidFile.getAbsolutePath(), String.valueOf(thisPid));

            // Try to be tidy. Note: This won't happen on exit if forcibly terminated, so we don't depend on it.
            pidFile.deleteOnExit();

            result = true;
        }

        return result;
    }

    public static @Nonnull String getHomePath()
    {
        // Returns a path like C:/Users/Person/
        return System.getProperty("user.home") + "/";
    }
}

class ProcessChecker
{
    private static final @Nonnull Logger log = LogManager.getLogger(io.cpucoin.core.platform.ProcessChecker.class.getName());

    static boolean isStillAllive(@Nonnull String pidStr)
    {
        String OS = System.getProperty("os.name").toLowerCase();
        String command;
        if (OS.contains("win"))
        {
            log.debug("Check alive Windows mode. Pid: [{}]", pidStr);
            command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\"";
        }
        else if (OS.contains("nix") || OS.contains("nux"))
        {
            log.debug("Check alive Linux/Unix mode. Pid: [{}]", pidStr);
            command = "ps -p " + pidStr;
        }
        else
        {
            log.warn("Unsupported OS: Check alive for Pid: [{}] return false", pidStr);
            return false;
        }
        return isProcessIdRunning(pidStr, command); // call generic implementation
    }

    private static boolean isProcessIdRunning(@Nonnull String pid, @Nonnull String command)
    {
        log.debug("Command [{}]", command);
        try
        {
            Runtime rt = Runtime.getRuntime();
            Process pr = rt.exec(command);

            InputStreamReader isReader = new InputStreamReader(pr.getInputStream());
            BufferedReader bReader = new BufferedReader(isReader);
            String strLine;
            while ((strLine = bReader.readLine()) != null)
            {
                if (strLine.contains(" " + pid + " "))
                {
                    return true;
                }
            }

            return false;
        }
        catch (Exception ex)
        {
            log.warn("Got exception using system command [{}].", command, ex);
            return true;
        }
    }
}

class FileUtil
{
    static void createDirectories(@Nonnull String dirPath) throws IOException
    {
        File dir = new File(dirPath);
        if (dir.mkdirs())   /* If false, directories already exist so nothing to do. */
        {
            if (!dir.exists())
            {
                throw new IOException(String.format("Failed to create directory (access permissions problem?): [%s]", dirPath));
            }
        }
    }

    static void createFile(@Nonnull String fullPathToFile, @Nonnull String contents)
    {
        try (PrintWriter writer = new PrintWriter(fullPathToFile, Charset.defaultCharset()))
        {
            writer.print(contents);
        }
        catch (IOException e)
        {
            throw new RuntimeException(String.format("Unable to create file at %s! %s", fullPathToFile, e.getMessage()), e);
        }
    }

    static @Nullable String readFile(@Nonnull File file)
    {
        try
        {
            try (BufferedReader fileReader = new BufferedReader(new FileReader(file)))
            {
                StringBuilder result = new StringBuilder();

                String line;
                while ((line = fileReader.readLine()) != null)
                {
                    result.append(line);
                    if (fileReader.ready())
                        result.append("\n");
                }
                return result.toString();
            }
        }
        catch (IOException e)
        {
            return null;
        }
    }
}

これを使用するには、次のように呼び出すだけです。

if (!SingleAppInstance.isOnlyInstanceOf("my-component"))
{
    // quit
}

これがお役に立てば幸いです。

0
djenning90

同じことを実現するために、Fileクラスにはすでに使用可能なJavaメソッドがあります。メソッドはdeleteOnExit()であり、JVMの終了時にファイルが自動的に削除されるようにします。ただし、強制終了には対応していません。 。強制終了の場合は、FileLockを使用する必要があります。

詳細については、 https://docs.Oracle.com/javase/7/docs/api/Java/io/File.html を確認してください。

したがって、mainメソッドで使用できるコードスニペットは次のようになります。

public static void main(String args[]) throws Exception {

    File f = new File("checkFile");

    if (!f.exists()) {
        f.createNewFile();
    } else {
        System.out.println("App already running" );
        return;
    }

    f.deleteOnExit();

    // whatever your app is supposed to do
    System.out.println("Blah Blah")
}
0
akshaya pandey