如何实现单个实例Java应用程序?


89

有时我会看到许多应用程序,例如msn,Windows Media Player等都是单实例应用程序(当用户在应用程序运行时执行时,将不会创建新的应用程序实例)。

在C#中,我Mutex为此使用类,但是我不知道如何在Java中执行此操作。


使用Java NIO的一种非常简单的方法,请参见完整的示例stackoverflow.com/a/20015771/185022
AZ_

Answers:


62

如果我相信这篇文章,可以通过:

让第一个实例尝试在localhost接口上打开侦听套接字。如果能够打开套接字,则假定这是要启动的应用程序的第一个实例。如果不是,则假定该应用程序的实例已在运行。新实例必须通知现有实例尝试启动,然后退出。现有实例在收到通知后将接管工作,并将事件触发给处理该操作的侦听器。

注意:Ahe在评论中提到使用InetAddress.getLocalHost()可能很棘手:

  • 它不能在DHCP环境中正常工作,因为返回的地址取决于计算机是否可以访问网络。
    解决的办法是打开与InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    可能与bug 4435662有关
  • 我还发现了错误4665037,它报告的预期结果为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


3
请注意,第一个解决方案有一个错误。我们最近发现InetAddress.getLocalHost()在DHCP环境中无法正常工作,因为返回的地址取决于计算机是否可以访问网络。解决方法是使用打开连接InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
阿合

2
@Ahe:很好。我在编辑后的答案中包含了您的评论以及Oracle-Sun错误报告参考。
VonC

3
根据JavaDoc InetAddress.getByName(null)返回的环回接口的地址。我猜这比手动指定127.0.0.1更好,因为从理论上讲,它也应在仅IPv6的环境中工作。
kayahr'2


1
@Puce当然,没问题:我已经恢复了那些链接。
VonC

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文件,只有一些类文件呢?
5YrsLaterDBA 2014年

2
真的有必要手动释放文件锁并在关机时关闭文件吗?进程终止时,这不是自动发生吗?
Natix 2014年

5
但是,如果电源中断并且计算机不运行关机钩而关机,会发生什么情况?该文件将保留,并且该应用程序将无法启动。
PetrHudeček2014年

6
@PetrHudeček很好。无论应用程序如何结束,文件锁都会被释放。如果关闭不当,那么它甚至具有允许应用程序在下次运行时实现此目的的好处。无论如何:锁定才是最重要的,而不是文件本身的存在。如果该文件仍然存在,则该应用程序仍将启动。
Dreamspace主席

@Robert:感谢您的解决方案,从那时起我一直在使用它。而现在,我将其扩展为也可以与另一个实例尝试启动的现有实例通信-使用文件夹WatchService!stackoverflow.com/a/36772436/3500521
Dreamspace总裁

9

如果是app。有一个GUI,可通过JWS启动并使用SingleInstanceService

更新资料

Oracle已弃用Java插件(applet和JWS应用程序都需要),并将其从JDK中删除。浏览器制造商已将其从其浏览器中删除。

因此,此答案已失效。仅将其留在此处以警告人们注意旧文档。


2
还应注意,似乎可以将新实例及其参数通知运行中的实例,从而可以轻松地将其传达给此类程序。
托尔比约恩Ravn的安徒生

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

我们为此使用文件锁定(在用户的应用程序数据目录中的魔术文件上获得排他锁),但我们主要希望阻止多个实例运行。

如果您试图让第二个实例将命令行参数args等传递给第一个实例,那么在localhost上使用套接字连接将用两只石头杀死两只鸟。通用算法:

  • 启动时,尝试在本地主机上的XXXX端口上打开侦听器
  • 如果失败,请在本地主机上的端口上打开编写器,并发送命令行args,然后关闭
  • 否则,请监听本地主机上的端口XXXXX。收到命令行参数时,就像使用该命令行启动应用程序一样处理它们。


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文件夹中创建文件锁定,并在每个随机端口上为每个唯一的appId创建一个服务器套接字,以允许在Java应用程序之间发送/接收消息。


我可以使用它来防止网络中Java应用程序的多实例吗?
也就是说,



2

您可以尝试使用偏好设置API。它是平台无关的。


我喜欢这个主意,因为API很简单,但也许某些病毒扫描程序不希望您更改注册表,因此会遇到与在具有软件防火墙的系统上使用RMI相似的问题。
卡尔

@Cal但是文件更改/锁定/等是同样的问题...你不觉得吗?
亚历克斯

2

限制单台机器甚至整个网络上实例数量的更通用的方法是使用多播套接字。

使用多播套接字,可以将消息广播到应用程序的任意数量的实例,其中一些实例可以位于公司网络中的物理远程计算机上。

这样,您可以启用多种类型的配置,以控制诸如

  • 每台机器一个或多个实例
  • 每个网络一个或多个实例(例如,控制客户端站点上的安装)

Java的组播支持是通过java.net包MulticastSocket时DatagramSocket类是主要工具。

注意:MulticastSocket不能保证传递数据包,因此您应该使用构建在多播套接字之上的工具,例如JGroups。JGroups的所有数据的保证交。它是一个单一的jar文件,具有非常简单的API。

JGroups已经存在了一段时间,并且在行业中有一些令人印象深刻的用途,例如,它支撑了JBoss的集群机制,可以将数据广播到集群的所有实例。

要使用JGroups来限制应用程序实例的数量(在计算机或网络上,可以说:限制为客户购买的许可证数量)从概念上讲非常简单:

  • 应用程序启动后,每个实例都尝试加入一个命名组,例如“ My Great App Group”。您将配置此组为允许0、1或N个成员
  • 当组成员数大于您为其配置的数量时。您的应用应拒绝启动。

1

您可以打开一个内存映射文件,然后查看该文件是否已经打开。如果已经打开,则可以从main返回。

其他方法是使用锁定文件(标准UNIX惯例)。另一种方法是在主电源启动后检查剪贴板中是否已有东西时将其放入剪贴板。

否则,您可以在侦听模式下打开套接字(ServerSocket)。首先尝试连接到hte套接字;如果无法连接,请打开服务器插槽。如果连接,则说明另一个实例已在运行。

因此,几乎所有系统资源都可用于了解应用程序正在运行。

BR,〜A


您有任何这些想法的代码吗?另外,如果我希望如果用户启动一个新实例,它将关闭所有先前的实例,该怎么办?
Android开发人员

1

我为此使用了套接字,并且取决于应用程序是在客户端还是在服务器端,其行为有所不同:

  • 客户端:如果实例已经存在(我无法在特定端口上侦听),我将传递应用程序参数并退出(您可能希望在前一个实例中执行一些操作),否则,我将启动应用程序。
  • 服务器端:如果实例已经存在,我将打印一条消息并退出,否则,我将启动该应用程序。

1
公共类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);
    }
}


1

编辑:不是使用这种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()带有捕获块就足够了,
Lorne侯爵

@EJP您是指已接受的答案,还是您在说什么?我已经搜索了很多没有失败的x平台无库解决方案,例如,因为某些套接字恰好已经被其他应用程序占用了。如果有解决方案,尤其是您所指的超级简单,那么我想知道这一点。
Dreamspace总裁

@EJP:我想再问一遍: 1)您所指的是什么琐碎的解决方案,就像您在我的头前像胡萝卜一样晃来晃去;2)如果是套接字解决方案,则接受的答案始于以下一种或多种:我的“插槽方法的缺点”的要点适用,以及3)如果适用,为什么尽管有这些缺点,您仍然还是建议在我的方法之上使用该方法。
Dreamspace总裁

@EJP:问题是,正如您确定的那样,您的声音有相当大的分量,但是所有证据表明我拥有的力量迫使我确信您的建议是错误的。瞧,我并不是坚持我的解决方案是正确的,而是所有我都是基于证据的机器。您是否没有看到自己的职位赋予社区责任,以填补开始的这种交流中缺失的难题?
Dreamspace总裁

@EJP:既然您一直没有得到您的回应,这就是我要假设的事实:服务器套接字解决方案的真相是,它确实存在严重缺陷,大多数选择它的原因可能是“其他也可以使用它。”,否则可能是不负责任的人欺骗他们使用它。我您之所以不以必要的说明来端庄,部分原因是您无法理解/为什么您从未质疑过这种方法,并且您也不想发表公开声明来揭示这一点。
Dreamspace总裁

1

Unique4j库可用于运行Java应用程序的单个实例并传递消息。您可以在https://github.com/prat-m​​an/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库。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.