¿Cómo implementar una aplicación Java de instancia única?

89

En algún momento veo muchas aplicaciones como msn, windows media player, etc. que son aplicaciones de instancia única (cuando el usuario ejecuta mientras la aplicación se está ejecutando, no se crea una nueva instancia de aplicación).

En C #, uso la Mutexclase para esto, pero no sé cómo hacerlo en Java.

Fuangwith S.
fuente
Un enfoque muy simple con java NIO ver el ejemplo completo stackoverflow.com/a/20015771/185022
AZ_

Respuestas:

62

Si creo este artículo , por:

que la primera instancia intente abrir un socket de escucha en la interfaz localhost. Si puede abrir el socket, se supone que esta es la primera instancia de la aplicación que se inicia. De lo contrario, se supone que ya se está ejecutando una instancia de esta aplicación. La nueva instancia debe notificar a la instancia existente que se intentó un lanzamiento y luego salir. La instancia existente toma el control después de recibir la notificación y dispara un evento al oyente que maneja la acción.

Nota: Ahe menciona en el comentario que usar InetAddress.getLocalHost()puede ser complicado:

  • no funciona como se esperaba en el entorno DHCP porque la dirección devuelta depende de si la computadora tiene acceso a la red.
    La solución fue abrir la conexión con InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Probablemente relacionado con el error 4435662 .
  • También encontré el error 4665037 que informa que los resultados esperados de getLocalHost: dirección IP de retorno de la máquina, vs. Resultados reales: retorno 127.0.0.1.

es sorprendente tener getLocalHostretorno 127.0.0.1en Linux pero no en Windows.


O puede usar ManagementFactoryobject. Como se explica aquí :

El getMonitoredVMs(int processPid)método recibe como parámetro el PID de la aplicación actual y captura el nombre de la aplicación que se llama desde la línea de comando, por ejemplo, la aplicación se inició desde la c:\java\app\test.jarruta, entonces la variable de valor es " c:\\java\\app\\test.jar". De esta manera, capturaremos solo el nombre de la aplicación en la línea 17 del código a continuación.
Después de eso, buscamos en JVM otro proceso con el mismo nombre, si lo encontramos y el PID de la aplicación es diferente, significa que es la segunda instancia de la aplicación.

JNLP ofrece también un SingleInstanceListener

VonC
fuente
3
Tenga en cuenta que la primera solución tiene un error. Recientemente descubrimos que InetAddress.getLocalHost()no funciona como se esperaba en el entorno DHCP porque la dirección devuelta depende de si la computadora tiene acceso a la red. La solución fue abrir la conexión con InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe
2
@Ahe: excelente punto. He incluido su comentario, así como referencias de informes de errores de Oracle-Sun en mi respuesta editada.
VonC
3
Según JavaDoc InetAddress.getByName(null)devuelve la dirección de la interfaz de bucle de retorno. Supongo que esto es mejor que especificar 127.0.0.1 manualmente porque, en teoría, esto también debería funcionar en entornos de solo IPv6.
kayahr
3
Consulte también Uso del servicio SingleInstanceService .
trashgod
1
@Puce Claro, no hay problema: he restaurado esos enlaces.
VonC
65

Utilizo el siguiente método en el método principal. Este es el método más simple, robusto y menos intrusivo que he visto, así que pensé en compartirlo.

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;
}
Robert
fuente
¿Cuál debe ser el parámetro "lockFile" para una aplicación de escritorio? el nombre del archivo jar de la aplicación? ¿Qué tal si no hay un archivo jar solo algunos archivos de clase?
5YrsLaterDBA
2
¿Es realmente necesario liberar manualmente el bloqueo del archivo y cerrar el archivo al apagarlo? ¿No sucede esto automáticamente cuando el proceso muere?
Natix
5
Pero, ¿qué sucede si se corta la energía y la computadora se apaga sin ejecutar el gancho de apagado? El archivo persistirá y la aplicación no se podrá ejecutar.
Petr Hudeček
6
@ PetrHudeček Está bien. No importa cómo finalice la aplicación, se liberará el bloqueo del archivo. Si no fue un apagado adecuado, entonces esto incluso tiene la ventaja de permitir que la aplicación se dé cuenta de esto en la próxima ejecución. En cualquier caso: lo que cuenta es el bloqueo, no la presencia del archivo en sí. Si el archivo aún está allí, la aplicación se iniciará de todos modos.
Presidente de Dreamspace
@Robert: Gracias por tu solución, la he estado usando desde entonces. Y justo ahora, lo extendí para comunicarme también con la instancia ya existente que otra instancia intentó iniciar, ¡usando la carpeta WatchService! stackoverflow.com/a/36772436/3500521
Presidente de Dreamspace
9

Si la aplicación. tiene una GUI, ejecútelo con JWS y use el SingleInstanceService.

Actualizar

El complemento de Java (necesario para los subprogramas y las aplicaciones JWS) fue obsoleto por Oracle y eliminado del JDK. Los fabricantes de navegadores ya lo habían eliminado de sus navegadores.

Entonces esta respuesta está extinta. Solo lo dejo aquí para advertir a las personas que buscan documentación antigua.

Andrew Thompson
fuente
2
También tenga en cuenta que parece que la instancia en ejecución puede ser informada de nuevas instancias y sus argumentos, lo que facilita la comunicación con dicho programa.
Thorbjørn Ravn Andersen
6

Sí, esta es una respuesta realmente decente para la aplicación de instancia única de eclipse RCP eclipse a continuación es mi código

en 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;
}
parvez Ahmad
fuente
5

Usamos el bloqueo de archivos para esto (agarre un bloqueo exclusivo en un archivo mágico en el directorio de datos de la aplicación del usuario), pero estamos interesados ​​principalmente en evitar que se ejecuten varias instancias.

Si está intentando que la segunda instancia pase argumentos de línea de comando, etc. a la primera instancia, entonces el uso de una conexión de socket en localhost matará dos pájaros de un tiro. Algoritmo general:

  • En el lanzamiento, intente abrir el oyente en el puerto XXXX en localhost
  • si falla, abra un escritor en ese puerto en localhost y envíe los argumentos de la línea de comando, luego apague
  • de lo contrario, escuche en el puerto XXXXX en localhost. Cuando reciba argumentos de la línea de comando, procéselos como si la aplicación se hubiera iniciado con esa línea de comando.
Kevin Day
fuente
5

He encontrado una solución, una explicación un poco caricaturesca, pero todavía funciona en la mayoría de los casos. Utiliza el antiguo archivo de bloqueo simple para crear cosas, pero en una vista bastante diferente:

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

Creo que será de ayuda para aquellos con una configuración de firewall estricta.

Icono
fuente
Sí, es una buena manera, ya que el bloqueo se liberaría si la aplicación fallara o algo así :)
LE GALL Benoît
5

Puede utilizar la biblioteca JUnique. Proporciona soporte para ejecutar aplicaciones java de instancia única y es de código abierto.

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

La biblioteca JUnique se puede utilizar para evitar que un usuario ejecute al mismo tiempo más instancias de la misma aplicación Java.

JUnique implementa bloqueos y canales de comunicación compartidos entre todas las instancias de JVM lanzadas por el mismo usuario.

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

Bajo el capó, crea bloqueos de archivos en la carpeta% USER_DATA% /. Junique y crea un socket de servidor en un puerto aleatorio para cada appId único que permite enviar / recibir mensajes entre aplicaciones Java.

kolobok
fuente
¿Puedo usar esto para evitar múltiples instancias de la aplicación Java en una red? también conocido como, solo se permite una instancia de mi aplicación dentro de toda mi red
Wuaner
2

Puede intentar utilizar la API de preferencias. Es una plataforma independiente.

Javamann
fuente
Me gusta esta idea ya que la API es simple, pero tal vez a algunos antivirus no les gustaría que cambiaras el registro, por lo que tienes problemas similares a los de usar RMI en sistemas con un software de firewall ... no estoy seguro.
Cal
@Cal Pero el mismo problema es con el cambio / bloqueo de archivos / etc ... ¿no crees?
Alex
2

Una forma más genérica de limitar el número de instancias en una sola máquina, o incluso en una red completa, es usar un socket de multidifusión.

El uso de un socket de multidifusión le permite transmitir un mensaje a cualquier cantidad de instancias de su aplicación, algunas de las cuales pueden estar en máquinas físicamente remotas a través de una red corporativa.

De esta manera, puede habilitar muchos tipos de configuraciones, para controlar cosas como

  • Una o muchas instancias por máquina
  • Una o muchas instancias por red (por ejemplo, controlar las instalaciones en el sitio de un cliente)

El soporte de multidifusión de Java se realiza a través del paquete java.net, siendo MulticastSocket y DatagramSocket las herramientas principales.

Nota : MulticastSocket no garantiza la entrega de paquetes de datos, por lo que debe utilizar una herramienta construida sobre sockets de multidifusión como JGroups . JGroups hace garantiza la entrega de todos los datos. Es un solo archivo jar, con una API muy simple.

JGroups ha existido por un tiempo y tiene algunos usos impresionantes en la industria, por ejemplo, respalda el mecanismo de agrupación de JBoss para transmitir datos a todas las instancias de un clúster.

Para usar JGroups, limitar la cantidad de instancias de una aplicación (en una máquina o red, digamos: a la cantidad de licencias que un cliente ha comprado) es conceptualmente muy simple:

  • Al iniciar su aplicación, cada instancia intenta unirse a un grupo con nombre, por ejemplo, "Mi gran grupo de aplicaciones". Habrá configurado este grupo para permitir 0, 1 o N miembros
  • Cuando el recuento de miembros del grupo es mayor de lo que ha configurado, su aplicación debería negarse a iniciarse.
johnm
fuente
1

Puede abrir un archivo asignado en memoria y luego ver si ese archivo ya está ABIERTO. si ya está abierto, puede regresar desde main.

Otra forma es usar archivos de bloqueo (práctica estándar de Unix). Una forma más es poner algo en el portapapeles cuando main se inicia después de verificar si ya hay algo en el portapapeles.

De lo contrario, puede abrir un socket en modo de escucha (ServerSocket). Primero intente conectarse al enchufe; si no puede conectarse, abra un socket de servidor. si se conecta, sabrá que ya se está ejecutando otra instancia.

Por lo tanto, se puede utilizar prácticamente cualquier recurso del sistema para saber que una aplicación se está ejecutando.

BR, ~ A

anjanb
fuente
¿tienes código para alguna de esas ideas? Además, ¿qué pasa si quiero que si el usuario inicia una nueva instancia, cierre todas las anteriores?
desarrollador de Android
1

Usé sockets para eso y, dependiendo de si la aplicación está en el lado del cliente o en el lado del servidor, el comportamiento es un poco diferente:

  • lado del cliente: si ya existe una instancia (no puedo escuchar en un puerto específico), pasaré los parámetros de la aplicación y saldré (es posible que desee realizar algunas acciones en la instancia anterior); si no, iniciaré la aplicación.
  • lado del servidor: si ya existe una instancia, imprimiré un mensaje y saldré, si no, iniciaré la aplicación.
adrian.tarau
fuente
1
public class SingleInstance {
    público estático final String LOCK = System.getProperty ("usuario.inicio") + File.separator + "test.lock";
    público estático final String PIPE = System.getProperty ("usuario.inicio") + File.separator + "test.pipe";
    marco JFrame estático privado = nulo;

    public static void main (String [] args) {
        tratar {
            FileChannel lockChannel = nuevo RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            tratar {
                flk = lockChannel.tryLock ();
            } atrapar (Throwable t) {
                t.printStackTrace ();
            }
            if (flk == null ||! flk.isValid ()) {
                System.out.println ("ya se está ejecutando, dejando un mensaje para canalizar y saliendo ...");
                FileChannel pipeChannel = null;
                tratar {
                    pipeChannel = nuevo RandomAccessFile (PIPE, "rw"). getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put (0, (byte) 1);
                    bb.force ();
                } atrapar (Throwable t) {
                    t.printStackTrace ();
                } finalmente {
                    if (pipeChannel! = null) {
                        tratar {
                            pipeChannel.close ();
                        } atrapar (Throwable t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // No soltamos el candado y cerramos el canal aquí, 
            // que se hará después de que la aplicación se bloquee o se cierre normalmente. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = null;
            tratar {
                pipeChannel = nuevo RandomAccessFile (PIPE, "rw"). getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (verdadero) {
                    byte b = bb.get (0);
                    si (b> 0) {
                        bb.put (0, (byte) 0);
                        bb.force ();
                        SwingUtilities.invokeLater (
                            new Runnable () {
                                public void run () {
                                    frame.setExtendedState (JFrame.NORMAL);
                                    frame.setAlwaysOnTop (verdadero);
                                    frame.toFront ();
                                    frame.setAlwaysOnTop (falso);
                                }
                            }
                        );
                    }
                    Thread.sleep (1000);
                }
            } atrapar (Throwable t) {
                t.printStackTrace ();
            } finalmente {
                if (pipeChannel! = null) {
                    tratar {
                        pipeChannel.close ();
                    } atrapar (Throwable t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } atrapar (Throwable t) {
            t.printStackTrace ();
        } 
    }

    public static void createAndShowGUI () {

        marco = nuevo JFrame ();
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setSize (800, 650);
        frame.getContentPane (). add (new JLabel ("VENTANA PRINCIPAL", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (nulo);
        frame.setVisible (verdadero);
    }
}

Jorge
fuente
1

EDITAR : En lugar de usar este enfoque WatchService, se podría usar un simple hilo de temporizador de 1 segundo para verificar si el indicadorFile.exists (). Bórrelo, luego lleve la aplicación a Front ().

EDITAR : Me gustaría saber por qué se votó en contra. Es la mejor solución que he visto hasta ahora. Por ejemplo, el enfoque del socket del servidor falla si otra aplicación ya está escuchando el puerto.

Simplemente descargue Microsoft Windows Sysinternals TCPView (o use netstat), inícielo, ordene por "Estado", busque el bloque de línea que dice "ESCUCHANDO", elija uno cuya dirección remota diga el nombre de su computadora, coloque ese puerto en su nuevo Socket ()-solución. En mi implementación, puedo producir fallas en todo momento. Y es lógico , porque es la base misma del enfoque. ¿O qué no obtengo con respecto a cómo implementar esto?

Por favor, infórmeme si me equivoco al respecto y en qué medida.

Mi punto de vista, que le pido que refute si es posible, es que se aconseja a los desarrolladores que utilicen un enfoque en el código de producción que fallará en al menos 1 de aproximadamente 60000 casos. Y si este punto de vista resulta ser correcta, entonces se puede absolutamente no ser que una solución presentada que no tiene este problema se downvoted y criticada por su cantidad de código.

Desventajas del enfoque de socket en comparación:

  • Falla si se elige el boleto de lotería incorrecto (número de puerto).
  • Falla en un entorno multiusuario: solo un usuario puede ejecutar la aplicación al mismo tiempo. (Mi enfoque tendría que cambiarse ligeramente para crear los archivos en el árbol de usuarios, pero eso es trivial).
  • Falla si las reglas del firewall son demasiado estrictas.
  • Hace que los usuarios sospechosos (que conocí en la naturaleza) se pregunten qué travesuras estás haciendo cuando tu editor de texto está reclamando un socket de servidor.

Acabo de tener una buena idea sobre cómo resolver el problema de comunicación de Java de nueva instancia a instancia existente de una manera que debería funcionar en todos los sistemas. Entonces, preparé esta clase en aproximadamente dos horas. Funciona a las mil maravillas: D

Se basa en el enfoque de bloqueo de archivos de Robert (también en esta página), que he usado desde entonces. Para decirle a la instancia que ya se está ejecutando que otra instancia intentó iniciarse (pero no lo hizo) ... se crea un archivo y se elimina inmediatamente, y la primera instancia usa WatchService para detectar este cambio de contenido de carpeta. No puedo creer que aparentemente esta sea una idea nueva, dado lo fundamental que es el problema.

Esto se puede cambiar fácilmente para simplemente crear y no eliminar el archivo, y luego se puede poner información en él que la instancia adecuada puede evaluar, por ejemplo, los argumentos de la línea de comando, y la instancia adecuada puede realizar la eliminación. Personalmente, solo necesitaba saber cuándo restaurar la ventana de mi aplicación y enviarla al frente.

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

Aquí está la clase:

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

}
Presidente de Dreamspace
fuente
No necesita cientos de líneas de código para resolver este problema. new ServerSocket()con un bloque de captura es bastante adecuado,
Marqués de Lorne
@EJP ¿Te refieres a la respuesta aceptada o de qué estás hablando? He buscado bastante una solución sin biblioteca extra de plataforma x que no falle, por ejemplo, porque un socket ya estaba ocupado por una aplicación diferente . Si hay una solución para esto, especialmente tan simple como te refieres, entonces me gustaría saberlo.
Presidente de Dreamspace
@EJP: Quiero preguntar de nuevo 1) qué solución trivial se está refiriendo a que ha estado colgando como una zanahoria delante de la cabeza, 2) en caso de que sea la solución toma la respuesta aceptada comienza con, si uno o más de Se aplican mis puntos de viñetas "Desventajas del enfoque de socket", y 3) si es así, por qué, a pesar de esas deficiencias, todavía recomendaría ese enfoque en lugar de uno como el mío.
Presidente de Dreamspace
@EJP: El problema es que tu voz tiene bastante peso, como seguro sabes, pero toda la evidencia que tengo me obliga a estar convencido de que tu consejo aquí es incorrecto. Mira, no estoy insistiendo en que mi solución sea correcta y todo eso, pero soy una máquina basada en evidencia. ¿No ve que su posición le da una responsabilidad a la comunidad para rellenar las piezas del rompecabezas que falta de esta comunicación que usted comenzó?
Presidente de Dreamspace
@EJP: Dado que lamentablemente no ha habido ninguna reacción de su parte, esto es lo que asumiré como un hecho: la verdad sobre la solución del socket del servidor es que realmente tiene fallas profundas , y la razón por la que la mayoría de los que la eligieron podría haber sido "Los otros use esto también. ", o podrían haber sido engañados para que lo usaran personas irresponsables. Yo supongo que parte de la razón por la que no nos dignifican con las explicaciones requeridas es que no se puede imaginar que / por qué nunca cuestionó este enfoque, y no quiere hacer una declaración pública que revela esto.
Presidente de Dreamspace
0

La biblioteca Unique4j se puede utilizar para ejecutar una sola instancia de una aplicación Java y pasar mensajes. Puede verlo en https://github.com/prat-man/unique4j . Es compatible con Java 1.6+.

Utiliza una combinación de bloqueos de archivos y bloqueos de puertos dinámicos para detectar y comunicarse entre instancias con el objetivo principal de permitir que solo se ejecute una instancia.

A continuación, se muestra un ejemplo simple de lo mismo:

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

    }

}

Descargo de responsabilidad: creé y mantengo la biblioteca Unique4j.

Pratanu Mandal
fuente