Como implementar um aplicativo Java de instância única?


89

Às vezes, vejo muitos aplicativos, como msn, windows media player etc, que são aplicativos de instância única (quando o usuário executa enquanto o aplicativo está executando, uma nova instância de aplicativo não é criada).

Em C #, eu uso Mutexclasse para isso, mas não sei como fazer isso em Java.


Uma abordagem muito simples com java NIO, consulte o exemplo completo stackoverflow.com/a/20015771/185022
AZ_

Respostas:


62

Se eu acreditar neste artigo , por:

tendo a primeira tentativa de instância de abrir um soquete de escuta na interface localhost. Se conseguir abrir o soquete, presume-se que esta seja a primeira instância do aplicativo a ser iniciada. Caso contrário, presume-se que uma instância desse aplicativo já esteja em execução. A nova instância deve notificar a instância existente de que houve uma tentativa de inicialização e, em seguida, sair. A instância existente assume após receber a notificação e dispara um evento para o ouvinte que trata da ação.

Nota: Ahe menciona no comentário que usar InetAddress.getLocalHost()pode ser complicado:

  • não funciona como esperado no ambiente DHCP porque o endereço retornado depende se o computador tem acesso à rede.
    A solução foi abrir a conexão com InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Provavelmente relacionado ao bug 4435662 .
  • Eu também encontrei o bug 4665037 que reporta que Resultados esperados de getLocalHost: retorno do endereço IP da máquina, vs. resultados reais: retorno 127.0.0.1.

é surpreendente ter getLocalHostretorno 127.0.0.1no Linux, mas não no Windows.


Ou você pode usar o ManagementFactoryobjeto. Conforme explicado aqui :

O getMonitoredVMs(int processPid)método recebe como parâmetro o PID da aplicação atual, e captura o nome da aplicação que é chamada na linha de comando, por exemplo, a aplicação foi iniciada a partir do c:\java\app\test.jarcaminho, então a variável de valor é " c:\\java\\app\\test.jar". Dessa forma, pegaremos apenas o nome do aplicativo na linha 17 do código abaixo.
Depois disso, procuramos na JVM outro processo com o mesmo nome, se o encontramos e o PID do aplicativo é diferente, significa que é a segunda instância do aplicativo.

JNLP oferece também um SingleInstanceListener


3
Esteja ciente de que a primeira solução tem um bug. Recentemente, descobrimos que InetAddress.getLocalHost()não funciona como esperado no ambiente DHCP porque o endereço retornado depende de o computador ter acesso à rede. A solução foi abrir a conexão com InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe

2
@Ahe: excelente ponto. Incluí seu comentário, bem como referências de relatório de bug Oracle-Sun em minha resposta editada.
VonC de

3
De acordo com o JavaDoc InetAddress.getByName(null)retorna o endereço da interface de loopback. Acho que isso é melhor do que especificar 127.0.0.1 manualmente porque, em teoria, isso também deve funcionar em ambientes somente IPv6.
kayahr


1
@Puce Claro, sem problemas: eu restaurei esses links.
VonC

65

Eu uso o seguinte método no método principal. Este é o método mais simples, mais robusto e menos intrusivo que já vi, então pensei em compartilhá-lo.

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

qual deve ser o parâmetro "lockFile" para um aplicativo de desktop? o nome do arquivo jar do aplicativo? que tal não haver arquivo jar, apenas alguns arquivos de classe?
5YrsLaterDBA

2
É realmente necessário liberar manualmente o bloqueio do arquivo e fechá-lo ao desligar? Isso não acontece automaticamente quando o processo morre?
Natix

5
Mas o que acontece se a energia acabar e o computador desligar sem executar o gancho de desligamento? O arquivo persistirá e o aplicativo não poderá ser iniciado.
Petr Hudeček

6
@ PetrHudeček Está tudo bem. Não importa como o aplicativo termina, o bloqueio do arquivo será liberado. Se não foi um desligamento adequado, isso ainda tem a vantagem de permitir que o aplicativo perceba isso na próxima execução. Em qualquer caso: O que conta é o cadeado, não a presença do próprio arquivo. Se o arquivo ainda estiver lá, o aplicativo será iniciado de qualquer maneira.
Presidente da Dreamspace

@Robert: Obrigado por sua solução, tenho usado desde então. E agora, estendi-o para comunicar também à instância já existente que outra instância tentou iniciar - usando a pasta WatchService! stackoverflow.com/a/36772436/3500521
Dreamspace Presidente

9

Se o app. tem uma GUI, inicie-o com JWS e use oSingleInstanceService .

Atualizar

O Java Plug-in (necessário para miniaplicativos e aplicativos JWS) foi descontinuado pela Oracle e removido do JDK. Os fabricantes de navegadores já o removeram de seus navegadores.

Portanto, esta resposta está extinta. Só deixando aqui para avisar as pessoas que olham a documentação antiga.


2
Observe também que parece que a instância em execução pode ser informada sobre novas instâncias e seus argumentos, facilitando a comunicação com esse programa.
Thorbjørn Ravn Andersen

6

Sim, esta é uma resposta realmente decente para o aplicativo de instância única eclipse RCP eclipse abaixo é meu código

em 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

Usamos o bloqueio de arquivo para isso (pegue um bloqueio exclusivo em um arquivo mágico no diretório de dados do aplicativo do usuário), mas estamos principalmente interessados ​​em evitar que várias instâncias sejam executadas.

Se você está tentando fazer com que a segunda instância passe argumentos de linha de comando, etc ... para a primeira instância, usar uma conexão de soquete no localhost matará dois coelhos com uma cajadada só. Algoritmo geral:

  • Na inicialização, tente abrir o listener na porta XXXX no localhost
  • se falhar, abra um gravador para essa porta no host local e envie os argumentos da linha de comando, em seguida, desligue
  • caso contrário, escute na porta XXXXX em localhost. Ao receber argumentos de linha de comando, processe-os como se o aplicativo tivesse sido iniciado com essa linha de comando.

5

Eu encontrei uma solução, uma explicação um pouco caricata, mas ainda funciona na maioria dos casos. Ele usa o arquivo de bloqueio antigo simples para criar coisas, mas em uma visão bastante diferente:

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

Acho que será uma ajuda para quem tem uma configuração de firewall rígida.


Sim, é uma boa maneira, pois o bloqueio seria liberado caso o aplicativo travasse ou algo assim :)
LE GALL Benoît

5

Você pode usar a biblioteca JUnique. Ele fornece suporte para a execução de aplicativos java de instância única e é de código aberto.

http://www.sauronsoftware.it/projects/junique/

A biblioteca JUnique pode ser usada para evitar que um usuário execute ao mesmo tempo mais instâncias do mesmo aplicativo Java.

JUnique implementa bloqueios e canais de comunicação compartilhados entre todas as instâncias JVM iniciadas pelo mesmo usuário.

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

Em segundo plano, ele cria bloqueios de arquivo na pasta% USER_DATA% /. Junique e cria um soquete de servidor em uma porta aleatória para cada appId exclusivo que permite o envio / recebimento de mensagens entre aplicativos java.


Posso usar isso para evitar várias instâncias do aplicativo java em uma rede? ou seja, apenas uma instância do meu aplicativo é permitida em toda a minha rede
Wuaner,



2

Você pode tentar usar a API de preferências. É independente de plataforma.


Gosto dessa ideia, pois a API é simples, mas talvez alguns antivírus não gostem que você altere o registro, de modo que você obtém problemas semelhantes aos do uso de RMI em sistemas com um firewall de software ... não tenho certeza.
Cal

@Cal Mas o mesmo problema é com alteração / bloqueio de arquivo / etc ... você não acha?
Alex,

2

Uma maneira mais genérica de limitar o número de instâncias em uma única máquina, ou mesmo em uma rede inteira, é usar um soquete multicast.

O uso de um soquete multicast permite que você transmita uma mensagem a qualquer quantidade de instâncias do seu aplicativo, algumas das quais podem estar em máquinas fisicamente remotas em uma rede corporativa.

Desta forma, você pode habilitar muitos tipos de configurações, para controlar coisas como

  • Uma ou várias instâncias por máquina
  • Uma ou várias instâncias por rede (por exemplo, controlar instalações em um site do cliente)

O suporte multicast do Java é via pacote java.net com MulticastSocket e DatagramSocket sendo as principais ferramentas.

Nota : MulticastSocket não garante a entrega de pacotes de dados, então você deve usar uma ferramenta construída em cima de sockets multicast como JGroups . JGroups faz entrega de garantia de todos os dados. É um único arquivo jar, com uma API muito simples.

O JGroups já existe há algum tempo e tem alguns usos impressionantes na indústria, por exemplo, ele sustenta o mecanismo de clustering do JBoss para transmitir dados para todas as instâncias de um cluster.

Para usar JGroups, para limitar o número de instâncias de um aplicativo (em uma máquina ou uma rede, digamos: para o número de licenças que um cliente comprou) é conceitualmente muito simples:

  • Na inicialização de seu aplicativo, cada instância tenta ingressar em um grupo nomeado, por exemplo, "Meu grande grupo de aplicativos". Você terá configurado este grupo para permitir 0, 1 ou N membros
  • Quando a contagem de membros do grupo é maior do que o que você configurou para ele, seu aplicativo deve se recusar a inicializar.

1

Você pode abrir um Arquivo Mapeado de Memória e ver se esse arquivo já está ABERTO. se já estiver aberto, você pode retornar do main.

Outra maneira é usar arquivos de bloqueio (prática padrão do Unix). Outra maneira é colocar algo na área de transferência quando o principal for iniciado, após verificar se algo já está na área de transferência.

Caso contrário, você pode abrir um soquete em modo de escuta (ServerSocket). Primeiro tente se conectar ao soquete hte; se você não conseguir se conectar, abra um socket de servidor. se você se conectar, saberá que outra instância já está em execução.

Portanto, praticamente qualquer recurso do sistema pode ser usado para saber que um aplicativo está em execução.

BR, ~ A


você tem código para alguma dessas idéias? Além disso, e se eu quiser que, se o usuário iniciar uma nova instância, ele feche todas as anteriores?
desenvolvedor Android de

1

Usei soquetes para isso e dependendo se o aplicativo está no lado do cliente ou no lado do servidor o comportamento é um pouco diferente:

  • lado cliente: se já existe uma instância (não consigo escutar em uma porta específica) vou passar os parâmetros do aplicativo e sair (você pode querer realizar algumas ações na instância anterior) se não, vou iniciar o aplicativo.
  • lado servidor: se já existe uma instância vou imprimir uma mensagem e sair, se não vou iniciar o aplicativo.

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";
    frame JFrame estático privado = null;

    public static void main (String [] args) {
        experimentar {
            FileChannel lockChannel = novo RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            experimentar {
                flk = lockChannel.tryLock ();
            } catch (Throwable t) {
                t.printStackTrace ();
            }
            if (flk == null ||! flk.isValid ()) {
                System.out.println ("já está em execução, deixando uma mensagem para o pipe e saindo ...");
                FileChannel pipeChannel = null;
                experimentar {
                    pipeChannel = novo 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 ();
                } finalmente {
                    if (pipeChannel! = null) {
                        experimentar {
                            pipeChannel.close ();
                        } catch (Throwable t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // Não liberamos o bloqueio e fechamos o canal aqui, 
            // que será feito após o aplicativo travar ou fechar normalmente. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = null;
            experimentar {
                pipeChannel = novo 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 ();
            } finalmente {
                if (pipeChannel! = null) {
                    experimentar {
                        pipeChannel.close ();
                    } catch (Throwable t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } catch (Throwable t) {
            t.printStackTrace ();
        } 
    }

    public static void createAndShowGUI () {

        frame = novo 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

EDIT : Em vez de usar esta abordagem WatchService, um thread de timer simples de 1 segundo poderia ser usado para verificar se o indicadorFile.exists (). Exclua-o e coloque o aplicativo em primeiro plano ().

EDIT : Eu gostaria de saber por que isso foi rejeitado. É a melhor solução que já vi até agora. Por exemplo, a abordagem de soquete do servidor falha se outro aplicativo já estiver escutando a porta.

Basta baixar o Microsoft Windows Sysinternals TCPView (ou usar netstat), iniciá-lo, classificar por "Estado", procurar o bloco de linha que diz "LISTENING", escolher um cujo endereço remoto diga o nome do seu computador, coloque essa porta em seu novo-Socket ()-solução. Na minha implementação, posso sempre produzir falhas. E é lógico , porque é a base da abordagem. Ou o que não estou conseguindo sobre como implementar isso?

Por favor, me informe se e como estou errado sobre isso!

Minha opinião - que estou pedindo que você refute se possível - é que os desenvolvedores estão sendo aconselhados a usar uma abordagem no código de produção que falhará em pelo menos 1 dos cerca de 60.000 casos. E se essa visão passa a ser correto, então ele pode absolutamente não ser que a solução apresentada que não tem este problema é downvoted e criticado por sua quantidade de código.

Desvantagens da abordagem de soquete em comparação:

  • Falha se o bilhete de loteria errado (número da porta) for escolhido.
  • Falha em ambiente multiusuário: apenas um usuário pode executar o aplicativo ao mesmo tempo. (Minha abordagem teria que ser ligeiramente alterada para criar o (s) arquivo (s) na árvore do usuário, mas isso é trivial.)
  • Falha se as regras de firewall forem muito rígidas.
  • Faz com que usuários suspeitos (que conheci na selva) se perguntem quais travessuras você está fazendo quando seu editor de texto está reivindicando um soquete de servidor.

Acabei de ter uma boa ideia de como resolver o problema de comunicação Java de nova instância para instância existente de uma forma que deve funcionar em todos os sistemas. Então, preparei essa aula em cerca de duas horas. Funciona como um encanto: D

É baseado na abordagem de bloqueio de arquivo de Robert (também nesta página), que usei desde então. Para informar à instância já em execução que outra instância tentou iniciar (mas não o fez) ... um arquivo é criado e imediatamente excluído, e a primeira instância usa o WatchService para detectar a alteração do conteúdo da pasta. Não acredito que aparentemente esta seja uma ideia nova, dado o quão fundamental é o problema.

Isso pode ser facilmente alterado para apenas criar e não excluir o arquivo e, em seguida, informações podem ser colocadas nele para que a instância adequada possa avaliar, por exemplo, os argumentos da linha de comando - e a instância adequada pode então executar a exclusão. Pessoalmente, eu só precisava saber quando restaurar a janela do meu aplicativo e enviá-la para frente.

Exemplo de uso:

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.");
}

Aqui está a aula:

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

}

Você não precisa de centenas de linhas de código para resolver esse problema. new ServerSocket()com um bloco de captura é bastante adequado,
Marquês de Lorne

@EJP Você está se referindo à resposta aceita ou do que está falando? Pesquisei bastante por uma solução de plataforma x sem biblioteca extra que não falha, por exemplo, porque algum soquete já estava ocupado por um aplicativo diferente . Se houver uma solução para isso - especialmente tão simples como você está se referindo - eu gostaria de saber sobre ela.
Presidente da Dreamspace,

@EJP: Eu quero perguntar novamente 1) a qual solução trivial você está se referindo e que tem pendurado como uma cenoura na minha cabeça, 2) caso seja a solução de encaixe com a qual a resposta aceita começa, se um ou mais dos meus pontos de bala "Desvantagens da abordagem de encaixe" se aplicam, e 3) se sim, por que, apesar dessas deficiências, você ainda recomendaria essa abordagem em vez de uma como a minha.
Dreamspace Presidente

@EJP: O problema é que sua voz tem bastante peso, como você sabe, mas todas as evidências que tenho me obrigam a estar convencido de que seu conselho está errado. Veja, não estou insistindo que minha solução esteja certa e tudo mais, mas sou uma máquina baseada em evidências. Você não vê que sua posição lhe dá a responsabilidade, perante a comunidade, de preencher as peças do quebra-cabeça que faltam nesta comunicação que você iniciou?
Dreamspace Presidente

@EJP: Como infelizmente não houve nenhuma reação sua, eis o que presumo como fato: a verdade sobre a solução de soquete do servidor é que ela realmente é profundamente falha , e a razão para a maioria dos que a escolheu pode ter sido "Os outros use isso também. ", ou eles podem ter sido enganados por pessoas irresponsáveis. Eu acho que parte da razão pela qual você não nos dignificou com as explicações necessárias é que você não consegue imaginar que / por que você nunca questionou essa abordagem, e você não quer fazer uma declaração pública revelando isso.
Presidente da Dreamspace

1

A biblioteca Unique4j pode ser usada para executar uma única instância de um aplicativo Java e transmitir mensagens. Você pode vê-lo em https://github.com/prat-man/unique4j . Suporta Java 1.6+.

Ele usa uma combinação de bloqueios de arquivo e bloqueios de porta dinâmica para detectar e se comunicar entre as instâncias com o objetivo principal de permitir que apenas uma instância seja executada.

A seguir está um exemplo simples do mesmo:

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

    }

}

Aviso Legal: Eu criei e mantenho a biblioteca Unique4j.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.