有时我会看到许多应用程序,例如msn,Windows Media Player等都是单实例应用程序(当用户在应用程序运行时执行时,将不会创建新的应用程序实例)。
在C#中,我Mutex
为此使用类,但是我不知道如何在Java中执行此操作。
Answers:
如果我相信这篇文章,可以通过:
让第一个实例尝试在localhost接口上打开侦听套接字。如果能够打开套接字,则假定这是要启动的应用程序的第一个实例。如果不是,则假定该应用程序的实例已在运行。新实例必须通知现有实例尝试启动,然后退出。现有实例在收到通知后将接管工作,并将事件触发给处理该操作的侦听器。
注意:Ahe在评论中提到使用InetAddress.getLocalHost()
可能很棘手:
- 它不能在DHCP环境中正常工作,因为返回的地址取决于计算机是否可以访问网络。
解决的办法是打开与InetAddress.getByAddress(new byte[] {127, 0, 0, 1})
;
可能与bug 4435662有关。
getLocalHost
:机器的IP地址返回vs.实际结果:return 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不同,则意味着这是第二个应用程序实例。
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;
}
如果是app。有一个GUI,可通过JWS启动并使用SingleInstanceService
。
Oracle已弃用Java插件(applet和JWS应用程序都需要),并将其从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;
}
我找到了一种解决方案,有点卡通化的解释,但在大多数情况下仍然有效。它使用普通的旧锁文件创建内容,但是视图却大不相同:
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文件夹中创建文件锁定,并在每个随机端口上为每个唯一的appId创建一个服务器套接字,以允许在Java应用程序之间发送/接收消息。
在Windows上,您可以使用launch4j。
J2SE 5.0或更高版本详细信息中支持的ManagementFactory类
但是现在我使用的是J2SE 1.4,我发现了这个http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/,但我从未进行过测试。你怎么看待这件事?
限制单台机器甚至整个网络上实例数量的更通用的方法是使用多播套接字。
使用多播套接字,可以将消息广播到应用程序的任意数量的实例,其中一些实例可以位于公司网络中的物理远程计算机上。
这样,您可以启用多种类型的配置,以控制诸如
Java的组播支持是通过java.net包与MulticastSocket时与DatagramSocket类是主要工具。
注意:MulticastSocket不能保证传递数据包,因此您应该使用构建在多播套接字之上的工具,例如JGroups。JGroups的做所有数据的保证交。它是一个单一的jar文件,具有非常简单的API。
JGroups已经存在了一段时间,并且在行业中有一些令人印象深刻的用途,例如,它支撑了JBoss的集群机制,可以将数据广播到集群的所有实例。
要使用JGroups来限制应用程序实例的数量(在计算机或网络上,可以说:限制为客户购买的许可证数量)从概念上讲非常简单:
您可以打开一个内存映射文件,然后查看该文件是否已经打开。如果已经打开,则可以从main返回。
其他方法是使用锁定文件(标准UNIX惯例)。另一种方法是在主电源启动后检查剪贴板中是否已有东西时将其放入剪贴板。
否则,您可以在侦听模式下打开套接字(ServerSocket)。首先尝试连接到hte套接字;如果无法连接,请打开服务器插槽。如果连接,则说明另一个实例已在运行。
因此,几乎所有系统资源都可用于了解应用程序正在运行。
BR,〜A
公共类SingleInstance { 公共静态最终字符串LOCK = System.getProperty(“ user.home”)+ File.separator +“ test.lock”; 公共静态最终字符串PIPE = System.getProperty(“ user.home”)+ File.separator +“ test.pipe”; 私有静态JFrame frame = null; 公共静态void main(String [] args){ 尝试{ FileChannel lockChannel =新的RandomAccessFile(LOCK,“ rw”)。getChannel(); FileLock flk = null; 尝试{ flk = lockChannel.tryLock(); } catch(Throwable t){ t.printStackTrace(); } 如果(flk == null ||!flk.isValid()){ System.out.println(“已在运行,正在向管道发送消息并退出...”); FileChannel pipeChannel = null; 尝试{ pipeChannel =新的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); } //我们不释放锁并在此处关闭通道, //这将在应用程序崩溃或正常关闭后完成。 SwingUtilities.invokeLater( 新的Runnable(){ 公共无效run(){ createAndShowGUI(); } } ); FileChannel pipeChannel = null; 尝试{ pipeChannel =新的RandomAccessFile(PIPE,“ rw”)。getChannel(); MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE,0,1); 而(true){ 字节b = bb.get(0); 如果(b> 0){ bb.put(0,(byte)0); bb.force(); SwingUtilities.invokeLater( 新的Runnable(){ 公共无效run(){ frame.setExtendedState(JFrame.NORMAL); frame.setAlwaysOnTop(true); frame.toFront(); frame.setAlwaysOnTop(false); } } ); } Thread.sleep(1000); } } catch(Throwable t){ t.printStackTrace(); }最后{ 如果(pipeChannel!= null){ 尝试{ pipeChannel.close(); } catch(Throwable t){ t.printStackTrace(); } } } } catch(Throwable t){ t.printStackTrace(); } } 公共静态无效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()。删除它,然后将应用程序置于Front()。
编辑:我想知道为什么这被否决了。到目前为止,这是最好的解决方案。例如,如果另一个应用程序恰巧已经在侦听端口,则服务器套接字方法将失败。
只需下载Microsoft Windows Sysinternals TCPView(或使用netstat),启动它,按“状态”排序,查找显示“ LISTENING”的行块,选择一个其远程地址显示您计算机名称的行,然后将该端口放入new-Socket ()-解。在执行它的过程中,我每次都会失败。这是合乎逻辑的,因为这是该方法的基础。还是我不知道如何实现这一目标?
请告知我是否以及对此有何误解!
我的观点(我想请您尽可能地反驳)是,建议开发人员在生产代码中使用一种方法,这种方法将在至少约60000种情况下失败。而且,如果这种观点恰好是正确的,那么绝对不可能因为所提出的不存在此问题的解决方案而对其代码量大打折扣。
比较套接字方法的缺点:
对于如何以一种适用于每个系统的方式解决新实例到现有实例的Java通信问题,我只有一个好主意。因此,我在大约两个小时内完成了这堂课。像魅力一样工作: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()
带有捕获块就足够了,
Unique4j库可用于运行Java应用程序的单个实例并传递消息。您可以在https://github.com/prat-man/unique4j上看到它。它支持Java 1.6+。
它使用文件锁和动态端口锁的组合来检测实例之间并在实例之间进行通信,其主要目标是仅允许一个实例运行。
以下是相同的简单示例:
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库。