単一インスタンスのJavaアプリケーションを実装する方法は?


89

msnやWindows Media Playerなど、シングルインスタンスアプリケーションである多くのアプリケーションが表示されることがあります(アプリケーションの実行中にユーザーが実行すると、新しいアプリケーションインスタンスは作成されません)。

C#では、Mutexこのためにクラスを使用していますが、Javaでこれを行う方法がわかりません。


JavaのNIOとの非常に単純なアプローチは、完全な例を参照stackoverflow.com/a/20015771/185022
AZ_

回答:


62

私がこの記事を信じるなら、

最初のインスタンスでlocalhostインターフェース上の待機ソケットを開こうとします。ソケットを開くことができる場合、これが起動されるアプリケーションの最初のインスタンスであると想定されます。そうでない場合は、このアプリケーションのインスタンスがすでに実行されていると想定されます。新しいインスタンスは、起動が試行されたことを既存のインスタンスに通知してから終了する必要があります。既存のインスタンスは、通知を受け取った後に引き継ぎ、アクションを処理するリスナーにイベントを発生させます。

注:Aheはコメントの中で、使い方InetAddress.getLocalHost()が難しい場合があると述べています:

  • 返されるアドレスは、コンピューターがネットワークにアクセスできるかどうかに依存するため、DHCP環境では期待どおりに機能しません。
    解決策は、との接続を開くことInetAddress.getByAddress(new byte[] {127, 0, 0, 1})でした。
    おそらくバグ4435662に関連しています。
  • また、バグ4665037も発見しました。これは、期待される結果getLocalHost:マシンのIPアドレスを返すのか、実際の結果:返すのを報告し127.0.0.1ます。

LinuxにgetLocalHost戻っ127.0.0.1てWindowsに戻っていないのは驚くべきことです。


または、ManagementFactoryオブジェクトを使用することもできます。ここで説明したように

このgetMonitoredVMs(int processPid)メソッドは、現在のアプリケーションのPIDをパラメーターとして受け取り、コマンドラインから呼び出されるアプリケーション名をキャッチします。たとえば、アプリケーションがc:\java\app\test.jarパスから開始された場合、値の変数は " c:\\java\\app\\test.jar"です。このようにして、以下のコードの17行目でアプリケーション名だけをキャッチします。
その後、JVMで同じ名前の別のプロセスを検索します。見つかった場合、アプリケーションのPIDが異なると、それが2番目のアプリケーションインスタンスであることを意味します。

JNLPは、 SingleInstanceListener


3
最初のソリューションにはバグがあることに注意してください。InetAddress.getLocalHost()返されたアドレスはコンピュータがネットワークにアクセスできるかどうかに依存するため、DHCP環境では期待どおりに機能しないことが最近発見されました。解決策は、との接続を開くことでしたInetAddress.getByAddress(new byte[] {127, 0, 0, 1});
アヘ

2
@アヘ:優れた点。私の編集した回答に、あなたのコメントとOracle-Sunのバグレポートの参照を含めました。
VonC

3
JavaDocによるとInetAddress.getByName(null)、ループバックインターフェースのアドレスを返します。これは手動で127.0.0.1を指定するよりも良いと思います。理論的には、これはIPv6のみの環境でも機能するはずだからです。
カヤール

3
SingleInstanceServiceサービスの使用も参照してください。
ゴミの神

1
@Puce確かに、問題ありません。これらのリンクを復元しました。
VonC、2015

65

メインメソッドでは以下のメソッドを使用しています。これは、私が見た中で最も単純で、最も堅牢で、邪魔にならない方法なので、共有したいと思いました。

private static boolean lockInstance(final String lockFile) {
    try {
        final File file = new File(lockFile);
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
        log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

デスクトップアプリケーションの「lockFile」パラメータはどうあるべきですか。アプリケーションjarファイル名?どのようにいくつかのクラスファイルだけのjarファイルはありませんか?
5年間LaterDBA 2014

2
シャットダウン時に手動でファイルロックを解除してファイルを閉じる必要はありますか?これは、プロセスが終了したときに自動的に発生しませんか?
Natix 2014年

5
しかし、電源が切れてシャットダウンフックを実行せずにコンピュータがシャットダウンした場合はどうなりますか?ファイルは保持され、アプリケーションは起動できなくなります。
PetrHudeček14年

6
@PetrHudeček大丈夫です。アプリケーションがどのように終了しても、ファイルロックは解除されます。これが適切なシャットダウンではなかった場合は、次の実行時にアプリケーションがこれを実現できるという利点もあります。いずれにせよ:ファイル自体の存在ではなく、ロックが重要です。ファイルがまだある場合、アプリケーションはとにかく起動します。
Dreamspace社長、2015

@ロバート:あなたの解決策をありがとう、それ以来ずっと私はそれを使ってきました。そして今、私はそれを拡張して、別のインスタンスが開始しようとした既存のインスタンスと通信するように拡張しました-フォルダーWatchServiceを使用して!stackoverflow.com/a/36772436/3500521
Dreamspace社長

9

アプリなら。GUIがあり、JWSで起動してを使用しSingleInstanceServiceます。

更新

Javaプラグイン(アプレットとJWSアプリの両方に必要)はOracleで非推奨になり、JDKから削除されました。ブラウザーの製造元は既にブラウザーから削除しています。

したがって、この回答は無効です。古いドキュメントを見る人に警告するために、ここに残しておくだけです。


2
また、実行中のインスタンスに新しいインスタンスとその引数を通知できるため、このようなプログラムとの通信が容易になるようです。
するThorbjörnRavnアンデルセン

6

はい、これはEclipse RCP Eclipseの本当に適切な答えです。以下のシングルインスタンスアプリケーションが私のコードです。

application.java

if(!isFileshipAlreadyRunning()){
        MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running.  Exiting.");
        return IApplication.EXIT_OK;
    } 


private static boolean isFileshipAlreadyRunning() {
    // socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
    // but this one is really great
    try {
        final File file = new File("FileshipReserved.txt");
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        //log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
       // log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

5

これにはファイルロックを使用します(ユーザーのアプリデータディレクトリにあるマジックファイルを排他的にロックします)が、主に複数のインスタンスが実行されないようにすることに関心があります。

2番目のインスタンスにコマンドライン引数などを最初のインスタンスに渡させる場合、localhostでソケット接続を使用すると、1石で2羽の鳥が殺されます。一般的なアルゴリズム:

  • 起動時に、localhostのポートXXXXでリスナーを開こうとします
  • 失敗した場合は、ローカルホスト上のそのポートにライターを開き、コマンドライン引数を送信して、シャットダウンします
  • それ以外の場合は、localhostのポートXXXXXで待機します。コマンドライン引数を受け取ったら、そのコマンドラインでアプリが起動されたかのように処理します。

5

私は解決策を見つけました、少し漫画的な説明ですが、ほとんどの場合まだ機能します。それはものを作成するプレーンな古いロックファイルを使用しますが、まったく異なる見方で:

http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html

ファイアウォール設定が厳しい方にはお勧めだと思います。


はい、アプリケーションがクラッシュした場合にロックが解放されるイベントになるため、これは良い方法です:)
LE GALLBenoîtJun

5

JUniqueライブラリを使用できます。単一インスタンスのJavaアプリケーションの実行をサポートし、オープンソースです。

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

JUniqueライブラリを使用すると、ユーザーが同じJavaアプリケーションの複数のインスタンスを同時に実行するのを防ぐことができます。

JUniqueは、同じユーザーが起動したすべてのJVMインスタンス間で共有されるロックと通信チャネルを実装します。

public static void main(String[] args) {
    String appId = "myapplicationid";
    boolean alreadyRunning;
    try {
        JUnique.acquireLock(appId, new MessageHandler() {
            public String handle(String message) {
                // A brand new argument received! Handle it!
                return null;
            }
        });
        alreadyRunning = false;
    } catch (AlreadyLockedException e) {
        alreadyRunning = true;
    }
    if (!alreadyRunning) {
        // Start sequence here
    } else {
        for (int i = 0; i < args.length; i++) {
            JUnique.sendMessage(appId, args[0]));
        }
    }
}

内部では、%USER_DATA%/。juniqueフォルダーにファイルロックを作成し、Javaアプリケーション間でメッセージを送受信できるようにする一意のappIdごとにランダムなポートにサーバーソケットを作成します。


これを使用して、ネットワーク内のJavaアプリケーションのマルチインスタンスを防止できますか?別名、私のアプリケーションの1つのインスタンスのみが私のネットワーク全体で許可されています
Wuaner '25


2

ManagementFactoryにクラスは、J2SE 5.0以降でサポートされている詳細

しかし、今はJ2SE 1.4を使用していますが、これをhttp://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/で見つけましたが、テストしていません。あなたはそれについてどう思いますか?


基本的には、上記の私の回答の最初のリンクで説明されているものだと思います... rbgrn.net/blog/2008/05/java-single-application-instance.html
VonC

2

Preferences APIを使用してみてください。プラットフォームに依存しません。


APIはシンプルなのでこのアイデアが好きですが、一部のウイルススキャナーはレジストリを変更したくないので、ソフトウェアファイアウォールのあるシステムでRMIを使用する場合と同様の問題が発生します。
Cal

@Calしかし、同じ問題がファイルの変更/ロック/などにあります...あなたは思いませんか?
Alex

2

単一のマシンまたはネットワーク全体のインスタンス数を制限するより一般的な方法は、マルチキャストソケットを使用することです。

マルチキャストソケットを使用すると、アプリケーションの任意の量のインスタンスにメッセージをブロードキャストできます。その一部は、企業ネットワーク上の物理的にリモートのマシン上にある場合があります。

このようにして、多くのタイプの構成を有効にして、次のようなものを制御できます

  • マシンごとに1つまたは複数のインスタンス
  • ネットワークごとに1つまたは複数のインスタンス(クライアントサイトでのインストールの制御など)

Javaのマルチキャストサポートは、MulticastSocketDatagramSocketがメインツールであるjava.netパッケージを介して行われます。

:MulticastSocketはデータパケットの配信を保証しないため、JGroupsなどのマルチキャストソケットの上に構築されたツールを使用する必要があります。JGroups すべてのデータの配信を保証します。これは、非常に単純なAPIを備えた単一のjarファイルです。

JGroupsはしばらく前から存在し、業界でいくつかの印象的な使用法があります。たとえば、JBossのクラスタリングメカニズムは、クラスタのすべてのインスタンスにデータをブロードキャストします。

JGroupsを使用して、(マシンまたはネットワーク上の)アプリのインスタンス数を制限することは、概念的には非常に簡単です。たとえば、顧客が購入したライセンスの数に制限します。

  • アプリケーションの起動時に、各インスタンスは「My Great App Group」などの名前付きグループに参加しようとします。このグループを構成して、0、1、またはNのメンバーを許可します
  • グループメンバー数が設定した数よりも多い場合、アプリは起動を拒否する必要があります。

1

メモリマップファイルを開いて、そのファイルが既にOPENかどうかを確認できます。すでに開いている場合は、メインから戻ることができます。

その他の方法は、ロックファイルを使用することです(UNIXの標準的な方法)。もう1つの方法は、何かがすでにクリップボードにあるかどうかを確認した後、mainが起動したときに何かをクリップボードに入れることです。

それ以外の場合は、リスンモード(ServerSocket)でソケットを開くことができます。最初にソケットへの接続を試みます。接続できない場合は、serverssocketを開きます。接続すると、別のインスタンスがすでに実行されていることがわかります。

そのため、アプリが実行中であることを知るために、ほとんどすべてのシステムリソースを使用できます。

BR、〜A


それらのアイデアのいずれかのコードはありますか?また、ユーザーが新しいインスタンスを開始した場合、以前のインスタンスがすべて閉じられるようにするにはどうすればよいですか?
Android開発者

1

そのためにソケットを使用しましたが、アプリケーションがクライアント側かサーバー側かによって、動作は少し異なります。

  • クライアント側:インスタンスが既に存在する場合(特定のポートでリッスンできない)アプリケーションのパラメーターを渡して終了します(前のインスタンスでいくつかのアクションを実行する場合があります)ない場合は、アプリケーションを起動します。
  • サーバー側:インスタンスがすでに存在する場合、メッセージを出力して終了します。存在しない場合は、アプリケーションを起動します。

1
パブリッククラスSingleInstance {
    public static final String LOCK = System.getProperty( "user.home")+ File.separator + "test.lock";
    public static final String PIPE = System.getProperty( "user.home")+ File.separator + "test.pipe";
    プライベート静的JFrameフレーム= null;

    public static void main(String [] args){
        {を試す
            FileChannel lockChannel = new RandomAccessFile(LOCK、 "rw")。getChannel();
            FileLock flk = null; 
            {を試す
                flk = lockChannel.tryLock();
            } catch(Throwable t){
                t.printStackTrace();
            }
            if(flk == null ||!flk.isValid()){
                System.out.println( "すでに実行中です。メッセージをパイプに残して終了します...");
                FileChannel pipeChannel = null;
                {を試す
                    pipeChannel = new RandomAccessFile(PIPE、 "rw")。getChannel();
                    MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE、0、1);
                    bb.put(0、(byte)1);
                    bb.force();
                } catch(Throwable t){
                    t.printStackTrace();
                } 最後に {
                    if(pipeChannel!= null){
                        {を試す
                            pipeChannel.close();
                        } catch(Throwable t){
                            t.printStackTrace();
                        }
                    } 
                }
                System.exit(0);
            }
            //ここではロックを解除してチャネルを閉じません。 
            //これは、アプリケーションがクラッシュまたは正常に閉じた後に行われます。 
            SwingUtilities.invokeLater(
                新しいRunnable(){
                    public void run(){
                        createAndShowGUI();
                    }
                }
            );

            FileChannel pipeChannel = null;
            {を試す
                pipeChannel = new RandomAccessFile(PIPE、 "rw")。getChannel();
                MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE、0、1);
                while(true){
                    バイトb = bb.get(0);
                    if(b> 0){
                        bb.put(0、(byte)0);
                        bb.force();
                        SwingUtilities.invokeLater(
                            新しいRunnable(){
                                public void run(){
                                    frame.setExtendedState(JFrame.NORMAL);
                                    frame.setAlwaysOnTop(true);
                                    frame.toFront();
                                    frame.setAlwaysOnTop(false);
                                }
                            }
                        );
                    }
                    Thread.sleep(1000);
                }
            }キャッチ(スロー可能なt){
                t.printStackTrace();
            } 最後に {
                if(pipeChannel!= null){
                    {を試す
                        pipeChannel.close();
                    } catch(Throwable t){
                        t.printStackTrace();
                    } 
                } 
            }
        } catch(Throwable t){
            t.printStackTrace();
        } 
    }

    public static void createAndShowGUI(){

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800、650);
        frame.getContentPane()。add(new JLabel( "MAIN WINDOW"、 
                    SwingConstants.CENTER)、BorderLayout.CENTER);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}


1

編集:このWatchServiceアプローチを使用する代わりに、単純な1秒のタイマースレッドを使用して、indicatorFile.exists()を確認できます。それを削除してから、アプリケーションをtoFront()にします。

編集:私はこれがなぜ反対票が投じられたのか知りたいのですが。これは、これまでに見た中で最高のソリューションです。たとえば、別のアプリケーションがすでにポートをリッスンしている場合、サーバーソケットアプローチは失敗します。

Microsoft Windows Sysinternals TCPViewをダウンロードして(またはnetstatを使用して)、「状態」でソートし、「LISTENING」という行ブロックを探し、リモートアドレスがコンピューターの名前を示しているものを1つ選び、そのポートを新しいソケットに挿入します。 ()-解決。私の実装では、毎回失敗することがあります。そしてそれは論理的です。なぜならそれがアプローチのまさに基盤だからです。または、これを実装する方法に関して私は何を得ていませんか?

私がこれについて間違っているかどうか、そしてどのように私に知らせてください!

私の見解-可能であれば反証するようにお願いしている-は、開発者は、約60000ケースの少なくとも1つで失敗するプロダクションコードでアプローチを使用するように助言されているということです。そして、もしこの見方がたまたま正しいならば、この問題を持たない提示された解決策がその投票量とそのコード量について批判されることは絶対にあり得ない。

比較によるソケットアプローチの短所:

  • 間違った宝くじ券(ポート番号)を選択すると失敗します。
  • マルチユーザー環境で失敗する:1人のユーザーのみが同時にアプリケーションを実行できます。(ユーザーツリーにファイルを作成するには、私のアプローチを少し変更する必要がありますが、それは簡単です。)
  • ファイアウォールルールが厳しすぎると失敗します。
  • テキストエディターがサーバーソケットを要求しているときに、疑わしいユーザー(私は実際に出会ったことのあるユーザー)があなたが今何をしているのか疑問に思います。

私は、すべてのシステムで機能するように、新しいインスタンスから既存のインスタンスへのJava通信の問題を解決する方法について素晴らしいアイデアを思いつきました。だから、私はこのクラスを約2時間で仕上げました。魅力のように機能します:D

それはRobertのファイルロックアプローチ(これもこのページにあります)に基づいています。既に実行中のインスタンスに、別のインスタンスが開始しようとした(ただし、開始しなかった)ことを通知するには...ファイルが作成され、すぐに削除されます。最初のインスタンスはWatchServiceを使用して、このフォルダーの内容の変更を検出します。問題がいかに根本的であるかを考えると、これは明らかに新しい考えであるとは信じられません。

これは、ファイルを作成するだけで削除しないように簡単に変更できます。次に、適切なインスタンスが評価できる情報(コマンドライン引数など)をファイルに入れて、適切なインスタンスが削除を実行できるようにします。個人的には、アプリケーションのウィンドウをいつ復元して前面に表示するかを知るだけで済みました。

使用例:

public static void main(final String[] args) {

    // ENSURE SINGLE INSTANCE
    if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
        System.exit(0);
    }

    // launch rest of application here
    System.out.println("Application starts properly because it's the only instance.");
}

private static void otherInstanceTriedToLaunch() {
    // Restore your application window and bring it to front.
    // But make sure your situation is apt: This method could be called at *any* time.
    System.err.println("Deiconified because other instance tried to start.");
}

ここにクラスがあります:

package yourpackagehere;

import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.*;




/**
 * SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
 * <p>
 * (file lock single instance solution by Robert https://stackoverflow.com/a/2002948/3500521)
 */
public enum SingleInstanceChecker {

    INSTANCE; // HAHA! The CONFUSION!


    final public static int POLLINTERVAL = 1000;
    final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
    final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");


    private boolean hasBeenUsedAlready = false;


    private WatchService watchService = null;
    private RandomAccessFile randomAccessFileForLock = null;
    private FileLock fileLock = null;


    /**
     * CAN ONLY BE CALLED ONCE.
     * <p>
     * Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
     * installed in that case.
     * <p>
     * Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
     * the temp file the return value will be true or false. This approach even works even if the virtual machine
     * process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
     * the file will still exist. (Thanks to Robert https://stackoverflow.com/a/2002948/3500521 for that solution!)
     * <p>
     * Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
     * is, it lacks some fundamental features. Don't worry, it has only been 25 years, it'll sure come eventually.
     *
     * @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
     *                                             changes the detect-file), the code will be executed. Could be used to
     *                                             bring the current (=old=only) instance to front. If null, then the
     *                                             watcher will not be installed at all, nor will the trigger file be
     *                                             created. (Null means that you just don't want to make use of this
     *                                             half of the class' purpose, but then you would be better advised to
     *                                             just use the 24 line method by Robert.)
     *                                             <p>
     *                                             BE CAREFUL with the code: It will potentially be called until the
     *                                             very last moment of the program's existence, so if you e.g. have a
     *                                             shutdown procedure or a window that would be brought to front, check
     *                                             if the procedure has not been triggered yet or if the window still
     *                                             exists / hasn't been disposed of yet. Or edit this class to be more
     *                                             comfortable. This would e.g. allow you to remove some crappy
     *                                             comments. Attribution would be nice, though.
     * @param executeOnAWTEventDispatchThread      Convenience function. If false, the code will just be executed. If
     *                                             true, it will be detected if we're currently on that thread. If so,
     *                                             the code will just be executed. If not so, the code will be run via
     *                                             SwingUtilities.invokeLater().
     * @return if this is the only instance
     */
    public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        if (hasBeenUsedAlready) {
            throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
        }
        hasBeenUsedAlready = true;

        final boolean ret = canLockFileBeCreatedAndLocked();

        if (codeToRunIfOtherInstanceTriesToStart != null) {
            if (ret) {
                // Only if this is the only instance, it makes sense to install a watcher for additional instances.
                installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
            } else {
                // Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
                //
                // Regarding "codeToRunIfOtherInstanceTriesToStart != null":
                // While creation/deletion of the file concerns THE OTHER instance of the program,
                // making it dependent on the call made in THIS instance makes sense
                // because the code executed is probably the same.
                createAndDeleteOtherInstanceWatcherTriggerFile();
            }
        }

        optionallyInstallShutdownHookThatCleansEverythingUp();

        return ret;
    }


    private void createAndDeleteOtherInstanceWatcherTriggerFile() {

        try {
            final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
            randomAccessFileForDetection.close();
            Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private boolean canLockFileBeCreatedAndLocked() {

        try {
            randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
            fileLock = randomAccessFileForLock.getChannel().tryLock();
            return fileLock != null;
        } catch (Exception e) {
            return false;
        }
    }


    private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        // PREPARE WATCHSERVICE AND STUFF
        try {
            watchService = FileSystems.getDefault().newWatchService();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        final File appFolder = new File("").getAbsoluteFile(); // points to current folder
        final Path appFolderWatchable = appFolder.toPath();


        // REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
        try {
            appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }


        // INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE'S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
        final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
        t.setDaemon(true);
        t.setName("directory content change watcher");
        t.start();
    }


    private void optionallyInstallShutdownHookThatCleansEverythingUp() {

        if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
            return;
        }

        final Thread shutdownHookThread = new Thread(() -> {
            try {
                if (fileLock != null) {
                    fileLock.release();
                }
                if (randomAccessFileForLock != null) {
                    randomAccessFileForLock.close();
                }
                Files.deleteIfExists(LOCKFILE.toPath());
            } catch (Exception ignore) {
            }
            if (watchService != null) {
                try {
                    watchService.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        Runtime.getRuntime().addShutdownHook(shutdownHookThread);
    }


    private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)

            try {
                Thread.sleep(POLLINTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            final WatchKey wk;
            try {
                wk = watchService.poll();
            } catch (ClosedWatchServiceException e) {
                // This situation would be normal if the watcher has been closed, but our application never does that.
                e.printStackTrace();
                return;
            }

            if (wk == null || !wk.isValid()) {
                continue;
            }


            for (WatchEvent<?> we : wk.pollEvents()) {

                final WatchEvent.Kind<?> kind = we.kind();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    System.err.println("OVERFLOW of directory change events!");
                    continue;
                }


                final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
                final File file = watchEvent.context().toFile();


                if (file.equals(DETECTFILE)) {

                    if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
                        codeToRunIfOtherInstanceTriesToStart.run();
                    } else {
                        SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
                    }

                    break;

                } else {
                    System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
                }

            }

            wk.reset();
        }
    }

}

この問題を解決するために数百行のコードは必要ありません。new ServerSocket()catchブロックは、非常に適切であると
ローンの侯爵

@EJP受け入れられた答えを参照していますか、それとも何について話しているのですか?失敗することのないxプラットフォームの追加ライブラリではないソリューションをかなり検索しました。たとえば、いくつかのソケットが別のアプリケーションによってすでに占有されていることが原因です。これに対する解決策がある場合-特にあなたが言及しているように非常に単純な場合-それについて知りたいのですが。
ドリームスペースプレジデント2016

@EJP:私はもう一度 質問たい1)あなたが私の頭の前でニンジンのようにぶら下がっていたことを言及している簡単な解決策、2)それが1つ以上の場合、受け入れられた回答が始まるソケットソリューションの場合私の「ソケットアプローチの短所」の箇条書きが適用されます。3)もしそうなら、なぜこれらの欠点にもかかわらず、私のようなアプローチよりもそのアプローチが推奨されるのでしょうか。
Dreamspace社長

@EJP:問題は、あなたの声はあなたが必ず認識しているように、かなりの重量がありますが、私が持っているすべての証拠ということで力が私がここにあなたのアドバイスが間違っていることを確信させます。ほら、私のソリューションが正しいことを主張しているわけではありませんが、私は証拠に基づくマシンです。あなたの立場が、あなたが始めたこのコミュニケーションの欠けているパズルのピースを埋める責任をコミュニティに与えているのを見ませんか?
Dreamspace社長、

@EJP:残念ながらあなたからの反応はありませんでしたので、ここで私が実際に仮定します:サーバーソケットソリューションの真実は、それが本当に欠陥があるということです。それを選択したほとんどの理由は、「その他これも使用してください。」私は推測し、あなたが必要な説明を私たちに威厳なかった理由の一部は、あなたがこのアプローチを疑問視したことがない理由を/測深することはできません、あなたはこれを明らかに公共の声明をしたくないということです。
Dreamspace社長、2016

1

Unique4jライブラリは、Javaアプリケーションの単一のインスタンスを実行してメッセージを渡すために使用できます。https://github.com/prat-m​​an/unique4jで確認できます。Java 1.6以降をサポートしています。

ファイルロックと動的ポートロックの組み合わせを使用して、1つのインスタンスのみの実行を許可することを主な目的として、インスタンス間の検出と通信を行います。

以下は同じの簡単な例です:

import tk.pratanumandal.unique4j.Unique4j;
import tk.pratanumandal.unique4j.exception.Unique4jException;

public class Unique4jDemo {

    // unique application ID
    public static String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";

    public static void main(String[] args) throws Unique4jException, InterruptedException {

        // create unique instance
        Unique4j unique = new Unique4j(APP_ID) {
            @Override
            public void receiveMessage(String message) {
                // display received message from subsequent instance
                System.out.println(message);
            }

            @Override
            public String sendMessage() {
                // send message to first instance
                return "Hello World!";
            }
        };

        // try to obtain lock
        boolean lockFlag = unique.acquireLock();

        // sleep the main thread for 30 seconds to simulate long running tasks
        Thread.sleep(30000);

        // try to free the lock before exiting program
        boolean lockFreeFlag = unique.freeLock();

    }

}

免責事項:私はUnique4jライブラリを作成して維持しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.