msnやWindows Media Playerなど、シングルインスタンスアプリケーションである多くのアプリケーションが表示されることがあります(アプリケーションの実行中にユーザーが実行すると、新しいアプリケーションインスタンスは作成されません)。
C#では、Mutex
このためにクラスを使用していますが、Javaでこれを行う方法がわかりません。
msnやWindows Media Playerなど、シングルインスタンスアプリケーションである多くのアプリケーションが表示されることがあります(アプリケーションの実行中にユーザーが実行すると、新しいアプリケーションインスタンスは作成されません)。
C#では、Mutex
このためにクラスを使用していますが、Javaでこれを行う方法がわかりません。
回答:
私がこの記事を信じるなら、
最初のインスタンスでlocalhostインターフェース上の待機ソケットを開こうとします。ソケットを開くことができる場合、これが起動されるアプリケーションの最初のインスタンスであると想定されます。そうでない場合は、このアプリケーションのインスタンスがすでに実行されていると想定されます。新しいインスタンスは、起動が試行されたことを既存のインスタンスに通知してから終了する必要があります。既存のインスタンスは、通知を受け取った後に引き継ぎ、アクションを処理するリスナーにイベントを発生させます。
注:Aheはコメントの中で、使い方InetAddress.getLocalHost()
が難しい場合があると述べています:
- 返されるアドレスは、コンピューターがネットワークにアクセスできるかどうかに依存するため、DHCP環境では期待どおりに機能しません。
解決策は、との接続を開くことInetAddress.getByAddress(new byte[] {127, 0, 0, 1})
でした。
おそらくバグ4435662に関連しています。
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
InetAddress.getLocalHost()
返されたアドレスはコンピュータがネットワークにアクセスできるかどうかに依存するため、DHCP環境では期待どおりに機能しないことが最近発見されました。解決策は、との接続を開くことでしたInetAddress.getByAddress(new byte[] {127, 0, 0, 1});
。
InetAddress.getByName(null)
、ループバックインターフェースのアドレスを返します。これは手動で127.0.0.1を指定するよりも良いと思います。理論的には、これはIPv6のみの環境でも機能するはずだからです。
メインメソッドでは以下のメソッドを使用しています。これは、私が見た中で最も単純で、最も堅牢で、邪魔にならない方法なので、共有したいと思いました。
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;
}
アプリなら。GUIがあり、JWSで起動してを使用しSingleInstanceService
ます。
Javaプラグイン(アプレットとJWSアプリの両方に必要)はOracleで非推奨になり、JDKから削除されました。ブラウザーの製造元は既にブラウザーから削除しています。
したがって、この回答は無効です。古いドキュメントを見る人に警告するために、ここに残しておくだけです。
はい、これは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;
}
これにはファイルロックを使用します(ユーザーのアプリデータディレクトリにあるマジックファイルを排他的にロックします)が、主に複数のインスタンスが実行されないようにすることに関心があります。
2番目のインスタンスにコマンドライン引数などを最初のインスタンスに渡させる場合、localhostでソケット接続を使用すると、1石で2羽の鳥が殺されます。一般的なアルゴリズム:
私は解決策を見つけました、少し漫画的な説明ですが、ほとんどの場合まだ機能します。それはものを作成するプレーンな古いロックファイルを使用しますが、まったく異なる見方で:
http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html
ファイアウォール設定が厳しい方にはお勧めだと思います。
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ごとにランダムなポートにサーバーソケットを作成します。
ManagementFactoryにクラスは、J2SE 5.0以降でサポートされている詳細
しかし、今はJ2SE 1.4を使用していますが、これをhttp://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/で見つけましたが、テストしていません。あなたはそれについてどう思いますか?
単一のマシンまたはネットワーク全体のインスタンス数を制限するより一般的な方法は、マルチキャストソケットを使用することです。
マルチキャストソケットを使用すると、アプリケーションの任意の量のインスタンスにメッセージをブロードキャストできます。その一部は、企業ネットワーク上の物理的にリモートのマシン上にある場合があります。
このようにして、多くのタイプの構成を有効にして、次のようなものを制御できます
Javaのマルチキャストサポートは、MulticastSocketとDatagramSocketがメインツールであるjava.netパッケージを介して行われます。
注:MulticastSocketはデータパケットの配信を保証しないため、JGroupsなどのマルチキャストソケットの上に構築されたツールを使用する必要があります。JGroups はすべてのデータの配信を保証します。これは、非常に単純なAPIを備えた単一のjarファイルです。
JGroupsはしばらく前から存在し、業界でいくつかの印象的な使用法があります。たとえば、JBossのクラスタリングメカニズムは、クラスタのすべてのインスタンスにデータをブロードキャストします。
JGroupsを使用して、(マシンまたはネットワーク上の)アプリのインスタンス数を制限することは、概念的には非常に簡単です。たとえば、顧客が購入したライセンスの数に制限します。
メモリマップファイルを開いて、そのファイルが既にOPENかどうかを確認できます。すでに開いている場合は、メインから戻ることができます。
その他の方法は、ロックファイルを使用することです(UNIXの標準的な方法)。もう1つの方法は、何かがすでにクリップボードにあるかどうかを確認した後、mainが起動したときに何かをクリップボードに入れることです。
それ以外の場合は、リスンモード(ServerSocket)でソケットを開くことができます。最初にソケットへの接続を試みます。接続できない場合は、serverssocketを開きます。接続すると、別のインスタンスがすでに実行されていることがわかります。
そのため、アプリが実行中であることを知るために、ほとんどすべてのシステムリソースを使用できます。
BR、〜A
そのためにソケットを使用しましたが、アプリケーションがクライアント側かサーバー側かによって、動作は少し異なります。
パブリッククラス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); } }
編集:このWatchServiceアプローチを使用する代わりに、単純な1秒のタイマースレッドを使用して、indicatorFile.exists()を確認できます。それを削除してから、アプリケーションをtoFront()にします。
編集:私はこれがなぜ反対票が投じられたのか知りたいのですが。これは、これまでに見た中で最高のソリューションです。たとえば、別のアプリケーションがすでにポートをリッスンしている場合、サーバーソケットアプローチは失敗します。
Microsoft Windows Sysinternals TCPViewをダウンロードして(またはnetstatを使用して)、「状態」でソートし、「LISTENING」という行ブロックを探し、リモートアドレスがコンピューターの名前を示しているものを1つ選び、そのポートを新しいソケットに挿入します。 ()-解決。私の実装では、毎回失敗することがあります。そしてそれは論理的です。なぜならそれがアプローチのまさに基盤だからです。または、これを実装する方法に関して私は何を得ていませんか?
私がこれについて間違っているかどうか、そしてどのように私に知らせてください!
私の見解-可能であれば反証するようにお願いしている-は、開発者は、約60000ケースの少なくとも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ブロックは、非常に適切であると
Unique4jライブラリは、Javaアプリケーションの単一のインスタンスを実行してメッセージを渡すために使用できます。https://github.com/prat-man/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ライブラリを作成して維持しています。