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

时间:2008-10-07 03:54:57

标签: java single-instance

有时候我看到许多应用程序,如msn,windows media player等是单实例应用程序(当用户执行应用程序运行时,将不会创建新的应用程序实例)。

在C#中,我使用Mutex类,但我不知道如何用Java做这个。

17 个答案:

答案 0 :(得分:61)

我在main方法中使用以下方法。这是我见过的最简单,最强大,最少侵入性的方法所以我认为我会分享它。

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;
}

答案 1 :(得分:59)

如果我相信article,请:

  

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

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

  
      
  • 在DHCP环境中无法正常工作,因为返回的地址取决于计算机是否具有网络访问权限   解决方案是使用 InetAddress.getByAddress(new byte[] {127, 0, 0, 1}) 打开连接;
      可能与bug 4435662相关。
  •   
  • 我还发现bug 4665037的报告比getLocalHost的预期结果:返回计算机的IP地址,与实际结果相对:返回127.0.0.1
  

令人惊讶的是,getLocalHost在Linux上返回127.0.0.1但在Windows上没有。{/ p>


或者您可以使用ManagementFactory对象。正如here所述:

  

getMonitoredVMs(int processPid)方法接收当前应用程序PID的参数,并捕获从命令行调用的应用程序名称,例如,应用程序从c:\java\app\test.jar路径启动,然后值变量为“c:\\java\\app\\test.jar”。这样,我们将在下面代码的第17行捕获应用程序名称   之后,我们在JVM中搜索具有相同名称的另一个进程,如果我们找到它并且应用程序PID不同,则意味着它是第二个应用程序实例。

JNLP还提供SingleInstanceListener

答案 2 :(得分:9)

如果应用程序。有一个GUI,用JWS启动它并使用SingleInstanceService。有关(演示和。)示例代码,请参阅demo. of the SingleInstanceService

答案 3 :(得分:7)

是的,对于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;
}

答案 4 :(得分:5)

我们使用文件锁定(在用户的app数据目录中获取魔术文件的独占锁定),但我们主要对防止多个实例的运行感兴趣。

如果你试图让第二个实例将命令行args等传递给第一个实例,那么在localhost上使用套接字连接就会一举两得。一般算法:

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

答案 5 :(得分: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创建服务器套接字。

答案 6 :(得分:5)

我找到了一个解决方案,有点卡通化的解释,但在大多数情况下仍然有效。它使用普通的旧锁文件创建东西,但是在一个完全不同的视图中:

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

我认为这对那些拥有严格防火墙设置的人有帮助。

答案 7 :(得分:4)

在Windows上,您可以使用launch4j

答案 8 :(得分:2)

您可以尝试使用Preferences API。它与平台无关。

答案 9 :(得分:2)

J2SE 5.0或更高版本detail

支持ManagementFactory类

但现在我使用J2SE 1.4,我找到了这个http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/,但我从未测试过。你觉得怎么样?

答案 10 :(得分:2)

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

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

通过这种方式,您可以启用多种类型的配置来控制

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

Java的多播支持是通过 java.net包 MulticastSocket &amp; DatagramSocket 是主要工具。

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

JGroups已经存在了一段时间,并且在业界有一些令人印象深刻的用法,例如它支持JBoss的集群机制向所有集群实例广播数据。

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

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

答案 11 :(得分:1)

我使用了套接字,并且根据应用程序是在客户端还是服务器端,行为有点不同:

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

答案 12 :(得分:1)


public class 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";
    private static JFrame frame = null;

    public static void main(String[] args) {
        try {
            FileChannel lockChannel = new RandomAccessFile(LOCK, "rw").getChannel();
            FileLock flk = null; 
            try {
                flk = lockChannel.tryLock();
            } catch(Throwable t) {
                t.printStackTrace();
            }
            if (flk == null || !flk.isValid()) {
                System.out.println("alread running, leaving a message to pipe and quitting...");
                FileChannel pipeChannel = null;
                try {
                    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();
                } finally {
                    if (pipeChannel != null) {
                        try {
                            pipeChannel.close();
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } 
                }
                System.exit(0);
            }
            //We do not release the lock and close the channel here, 
            //  which will be done after the application crashes or closes normally. 
            SwingUtilities.invokeLater(
                new Runnable() {
                    public void run() {
                        createAndShowGUI();
                    }
                }
            );

            FileChannel pipeChannel = null;
            try {
                pipeChannel = new RandomAccessFile(PIPE, "rw").getChannel();
                MappedByteBuffer bb = pipeChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    byte b = bb.get(0);
                    if (b > 0) {
                        bb.put(0, (byte)0);
                        bb.force();
                        SwingUtilities.invokeLater(
                            new Runnable() {
                                public void run() {
                                    frame.setExtendedState(JFrame.NORMAL);
                                    frame.setAlwaysOnTop(true);
                                    frame.toFront();
                                    frame.setAlwaysOnTop(false);
                                }
                            }
                        );
                    }
                    Thread.sleep(1000);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                if (pipeChannel != null) {
                    try {
                        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);
    }
}

答案 13 :(得分:1)

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

其他方法是使用锁文件(标准的unix练习)。另一种方法是在检查剪贴板中是否已存在某些内容后,在主要启动时将某些内容放入剪贴板。

否则,您可以在侦听模式(ServerSocket)中打开套接字。首先尝试连接到hte socket;如果你无法连接,那么打开一个serversocket。如果你连接,那么你知道另一个实例已经在运行。

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

BR, 〜A

答案 14 :(得分:0)

编辑:可以使用简单的1秒计时器线程来检查indicatorFile.exists(),而不是使用此WatchService方法。删除它,然后将应用程序带到前面()。

编辑:我想知道为什么这是低估的。这是迄今为止我见过的最好的解决方案。例如。如果另一个应用程序恰好已经在侦听端口,则服务器套接字方法会失败。

只需下载Microsoft Windows Sysinternals TCPView(或使用netstat),启动它,按“状态”排序,查找“LISTENING”的行块,选择一个远程地址表示您的计算机名称,那个端口进入你的new-Socket() - 解决方案。在我的实现中,我每次都会产生失败。它是逻辑,因为它是该方法的基础。或者我没有得到关于如何实现这个的内容?

如果我错了,请通知我!

我的观点 - 我要求你在可能的情况下反驳 - 是建议开发人员在生产代码中使用一种方法,在至少60000个案例中至少有一个会失败。如果这个观点恰好是正确的,那么它绝对不会是一个没有出现这个问题的解决方案被低估并因其代码量而受到批评。

比较套接字方法的缺点:

  • 如果选择了错误的彩票(端口号),则会失败。
  • 在多用户环境中失败:只有一个用户可以同时运行该应用程序。 (我的方法必须略微改变才能在用户树中创建文件,但这很简单。)
  • 如果防火墙规则过于严格,则会失败。
  • 让可疑用户(我在野外见过面)想知道当你的文本编辑器声称服务器套接字时你有什么恶作剧。

我对如何以适用于每个系统的方式解决新实例到现有实例的Java通信问题有了一个很好的想法。所以,我在大约两个小时内掀起了这堂课。像魅力一样:D

它基于Robert的文件锁定方法(也在此页面上),此后我一直使用它。告诉已经运行的实例另一个实例尝试启动(但没有)......创建并立即删除文件,第一个实例使用WatchService检测此文件夹内容更改。鉴于问题的根本性,我无法相信这显然是一个新想法。

这可以很容易地改为 create 而不是删除文件,然后可以将正确的实例可以评估的信息放入其中,例如,命令行参数 - 然后正确的实例可以执行删除。就个人而言,我只需要知道何时恢复我的应用程序窗口并将其发送到前面。

使用示例:

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();
        }
    }

}

答案 15 :(得分:0)

我为此专门编写了一个名为Unique4j的Java库。您可以在https://github.com/prat-man/unique4j上看到它。该库在Maven Central上也可用,以方便访问和集成。

它是跨平台兼容的,并且支持Java 1.6+。该库已获得Apache 2.0的许可,这使其非常适合与开源和封闭项目一起使用。

它使用文件锁和动态端口锁的组合来检测实例之间并进行通信,其主要目标是仅允许一个实例运行。

以下是Unique4j的用法和功能的简单示例。该库提供了更多的自定义和功能,可以覆盖这些自定义和功能以获得更多的功能。

import tk.pratanumandal.unique4j.Unique;
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 {
        // create unique instance
        Unique unique = new Unique(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();

        // perform long running tasks here
        ...

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

我为自己的项目之一创建了它,然后将其发布为库。希望有人觉得它有用。

答案 16 :(得分:0)

我为此https://sanyarnd.github.io/applocker

写了一个专用的库

它基于文件通道锁定,因此它不会阻塞端口号,也不会在断电(进程终止后释放通道)时死锁应用程序。

库本身是轻量级的,并且具有流畅的API。

它受http://www.sauronsoftware.it/projects/junique/的启发,但它基于文件通道。还有其他一些新功能。