如何实现单个实例Java应用程序?
有时我看到许多应用程序,如MSN,Windows媒体播放器等单实例应用程序(当用户执行应用程序运行时,不会创build一个新的应用程序实例)。
在C#中,我使用Mutex
类,但我不知道如何在Java中做到这一点。
如果我相信这篇文章 ,通过:
让第一个实例尝试在本地主机接口上打开监听套接字。 如果能够打开套接字,则假定这是要启动的应用程序的第一个实例。 如果不是,则假设该应用程序的一个实例已经在运行。 新实例必须通知现有实例发起尝试,然后退出。 现有的实例在接收到通知后接pipe,并将事件触发到处理该操作的侦听器。
注意: Ahe在评论中提到使用InetAddress.getLocalHost()
会很棘手:
- 它在DHCP环境中不能正常工作,因为返回的地址取决于计算机是否具有networking访问权限。
解决方法是打开InetAddress.getByAddress(new byte[] {127, 0, 0, 1})
。
可能与错误4435662有关 。
- 我还发现报告错误4665037比
getLocalHost
预期结果:返回机器的IP地址,与实际结果:返回127.0.0.1
。
getLocalHost
在Linux上返回127.0.0.1
,而不是在Windows上是令人惊讶的。
或者你可以使用ManagementFactory
对象。 如这里所解释的:
getMonitoredVMs(int processPid)
方法接收当前应用程序的PID作为参数,并捕获从命令行调用的应用程序名称,例如,应用程序是从c:\java\app\test.jar
path启动的,则值variables是“c:\\java\\app\\test.jar
”。 这样,我们将在下面的代码的第17行捕获应用程序的名称。
之后,我们search另一个同名的进程,如果我们发现它和应用程序的PID是不同的,这意味着这是第二个应用程序实例。
JNLP还提供了一个SingleInstanceListener
我在主要方法中使用以下方法。 这是我见过的最简单,最健壮,最不干扰的方法,所以我想我会分享它。
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
。 看演示。 (demo.and )示例代码的SingleInstanceService 。
是的,这是一个真正体面的答案日食RCP日食单实例应用程序下面是我的代码
在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; }
我们使用文件locking(在用户的应用程序数据目录中获取一个魔术文件的独占锁),但是我们主要关心的是防止多个实例运行。
如果你想让第二个实例通过命令行参数等等,那么在本地主机上使用套接字连接将会一石二鸟。 一般algorithm:
- 在启动时,尝试在localhost上的端口XXXX上打开侦听器
- 如果失败,请在本地主机上打开一个作者到该端口并发送命令行参数,然后closures
- 否则,监听本地主机上的端口XXXXX。 当收到命令行参数时,就像使用该命令行启动应用程序一样处理它们。
在Windows上,您可以使用launch4j 。
我find了一个解决scheme,有点卡通的解释,但仍然在大多数情况下工作。 它使用普通的旧锁文件创build的东西,但在一个完全不同的观点:
http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html
我认为这将是一个严格的防火墙设置的帮助。
您可以使用JUnique库。 它为运行单实例Java应用程序提供支持,并且是开源的。
http://www.sauronsoftware.it/projects/junique/
JUnique库可用于防止用户同时运行同一个Java应用程序的更多实例。
JUnique实现了由同一用户启动的所有JVM实例之间共享的locking和通信通道。
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文件夹中创build文件锁,并为每个唯一的appId创build一个随机端口的服务器套接字,允许在Java应用程序之间发送/接收消息。
J2SE 5.0或更高版本支持的ManagementFactory类
但现在我使用J2SE 1.4,我发现这一个http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/,但我从来没有testing。; 你怎么看待这件事?
您可以尝试使用首选项API。 它是平台独立的。
您可以打开一个内存映射文件,然后查看该文件是否已经打开。 如果已经打开,你可以从main返回。
其他方法是使用locking文件(标准的UNIX练习)。 还有一种方法是在检查剪贴板中是否有东西时,在主启动时将某些内容放入剪贴板。
否则,您可以在侦听模式下打开一个套接字(ServerSocket)。 首先尝试连接到套接字; 如果你不能连接,那么打开一个serversocket。 如果你连接,那么你知道另一个实例已经在运行。
所以,几乎任何系统资源都可以用来了解应用程序正在运行。
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(“跑步,留言给pipe道,退出...”); 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(); 最后{ 如果(pipeChannel!= null){ 尝试{ pipeChannel.close(); catch(Throwable t){ t.printStackTrace(); } } } System.exit(0); } //我们不会在这里解锁并closures频道, //在应用程序崩溃或正常closures之后完成。 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); 如果(b> 0){ bb.put(0,(byte)0); bb.force(); SwingUtilities.invokeLater( 新的Runnable(){ public void run(){ frame.setExtendedState(JFrame.NORMAL); frame.setAlwaysOnTop(真); frame.toFront(); frame.setAlwaysOnTop(假); } } ); } 了Thread.sleep(1000); } catch(Throwable t){ t.printStackTrace(); 最后{ 如果(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(真); } }
限制单个机器甚至整个networking上的实例数量的更通用的方法是使用多播套接字。
使用组播套接字,可以将消息广播到任意数量的应用程序实例中,其中一些实例可以位于企业networking的物理远程计算机上。
通过这种方式,您可以启用多种types的configuration,以控制类似的情况
- 每台机器一个或多个实例
- 每个networking一个或多个实例(例如控制客户端站点上的安装)
Java的组播支持是通过java.net包 , MulticastSocket & DatagramSocket是主要的工具。
注意 :MulticastSocket不能保证数据包的传送,所以你应该使用一个build立在多播套接字之上的工具,比如JGroups 。 JGroups确保提供所有数据。 它是一个单独的jar文件,具有非常简单的API。
JGroups已经有一段时间了,在业界有一些令人印象深刻的用法,例如它支持JBoss的集群机制向所有集群实例广播数据。
要使用JGroups,限制应用程序实例(在一台机器或一个networking上)的数量在概念上非常简单:
- 启动应用程序后,每个实例都会尝试join一个命名组,例如“我的优秀应用程序组”。 您将configuration此组以允许0,1或N个成员
- 当组员数量大于您为其configuration的组员数量时,您的应用应该拒绝启动。
编辑 :我想知道为什么这是downvoted。 这是迄今为止我看到的最好的解决scheme。 例如,如果另一个应用程序碰巧已经在监听端口,则服务器套接字方法将失败。
只要下载Microsoft Windows Sysinternals TCPView (或使用netstat),启动它,按“状态”sorting,寻找“LISTENING”的行块,select远程地址表示您的计算机名称,将该端口放入您的新的Socket ()-解。 在我的实施中,我每次都会产生失败。 这是合乎逻辑的 ,因为它是方法的基础。 或者我没有得到如何实现这个?
请告诉我,如果我怎么错了!
我的观点 – 如果可能的话,我要求你反驳一下 – 是build议开发者在生产代码中使用一种方法,在大约60000个案例中至less有一个会失败。 如果这个观点恰好是正确的,那么就不可能是一个没有这个问题的解决scheme因为它的代码量而被低估和批评。
套接字方式的缺点比较:
- 如果select错误的彩票(端口号),则失败。
- 在多用户环境中失败:只有一个用户可以同时运行应用程序。 (我的方法将不得不稍微改变,以在用户树中创build文件,但这是微不足道的。)
- 如果防火墙规则太严格,则会失败。
- 使可疑的用户(我在野外遇到的)不知道当你的文本编辑器声称服务器套接字时,你要做什么。
我对于如何解决新实例到现有实例Java通信问题有一个很好的想法,这种方式应该适用于每个系统。 所以,我在大约两个小时的时间里就把这个课上了。 像魅力一样工作:D
这是基于罗伯特的文件locking方法(也在这个页面上),这是我从那以后使用的。 要告诉已经运行的实例,另一个实例试图启动(但没有)…文件被创build并立即删除,并且第一个实例使用WatchService来检测这个文件夹内容的变化。 考虑到问题的根本原因,我不能相信这显然是一个新的想法。
这可以很容易地改变,只是创build和不删除文件,然后信息可以放入适当的实例可以评估,例如命令行参数 – 然后适当的实例可以执行删除。 就我个人而言,我只需要知道何时恢复我的应用程序的窗口,并将其发送到前面。
使用示例:
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."); }
这是这个class级:
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 eg 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 eg 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(); } } }