Pregunta

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 ejecuta una nueva instancia de aplicación, no se creará).

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

¿Fue útil?

Solución

Si creo en este artículo , por:

  

la primera instancia intenta abrir un conector de escucha en la interfaz localhost. Si es capaz de abrir el socket, se supone que esta es la primera instancia de la aplicación que se iniciará. 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 el uso de InetAddress.getLocalHost () puede ser complicado:

  
      
  • no funciona como se espera 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 (nuevo byte [] {127, 0, 0, 1}) ;
      Probablemente relacionado con error 4435662 .
  •   
  • También encontré error 4665037 que informa de los resultados esperados de getLocalHost : devuelve la dirección IP de la máquina, en comparación con los resultados reales: devuelve 127.0.0.1 .
  

es sorprendente que getLocalHost devuelva 127.0.0.1 en Linux pero no en Windows.


O puede usar ManagementFactory . Como se explica en here :

  

El método getMonitoredVMs (int processPid) 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 comandos, por ejemplo, la aplicación se inició desde c: \ java \ app \ test.jar , 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 otro proceso con el mismo nombre en JVM, 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

Otros consejos

Uso 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é que lo compartiría.

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

Si la aplicación. tiene una GUI, inicie con JWS y use el SingleInstanceService . Consulte la demostración . del SingleInstanceService para el código de ejemplo (demo. y).

Sí, esta es una respuesta realmente decente para la aplicación de instancia única eclipse RCP eclipse abajo está 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;
}

Usamos el bloqueo de archivos para esto (obtenga 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 la línea de comando args, etc ... a la primera instancia, el uso de una conexión de zócalo en localhost matará dos pájaros de un tiro. Algoritmo general:

  • En el lanzamiento, intente abrir la escucha 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 apáguelo
  • de lo contrario, escuche en el puerto XXXXX en localhost. Cuando reciba argumentos de la línea de comandos, procételos como si la aplicación se hubiera iniciado con esa línea de comandos.

He encontrado una solución, una explicación un tanto caricaturesca, pero aún funciona en la mayoría de los casos. Utiliza el archivo de bloqueo anterior sin formato, pero en una vista muy diferente:

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

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

Puedes usar la librería 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 usar para evitar que un usuario se ejecute al mismo tiempo   Tiempo más instancias de la misma aplicación Java.

     

JUnique implementa bloqueos y canales de comunicación compartidos entre todos   las instancias 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.

En Windows, puede usar launch4j .

La clase ManagementFactory es compatible con J2SE 5.0 o posterior detail

pero ahora uso J2SE 1.4 y encontré este http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/ pero Nunca lo pruebo. ¿Qué piensas al respecto?

Puedes intentar usar la API de preferencias. Es plataforma independiente.

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 varias instancias por red (por ejemplo, control de instalaciones en un sitio cliente)

El soporte de multidifusión de Java es a través de java.net package con MulticastSocket & amp; DatagramSocket son las herramientas principales.

Nota : MulticastSocket no garantiza la entrega de paquetes de datos, por lo que debe usar una herramienta construida sobre zócalos de multidifusión como JGroups . JGroups 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 agrupamiento de JBoss para transmitir datos a todas las instancias de un clúster.

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

  • Al iniciar la aplicación, cada instancia intenta unirse a un grupo nombrado, por ejemplo, "Mi gran grupo de aplicaciones". Habrá configurado este grupo para permitir 0, 1 o N miembros
  • Cuando el número de miembros del grupo es mayor que lo que ha configurado para él, su aplicación debe negarse a iniciarse.

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

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

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

Entonces, casi cualquier recurso del sistema puede usarse para saber que una aplicación se está ejecutando.

BR, ~ A

Usé sockets para eso y dependiendo de si la aplicación está en el lado del cliente o 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), de lo contrario, iniciaré la aplicación.
  • lado del servidor: si ya existe una instancia, imprimiré un mensaje y saldré, si no, iniciaré la aplicación.
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);
    }
}

EDIT : en lugar de utilizar este enfoque de WatchService, se podría usar un hilo de temporizador simple de 1 segundo para verificar si existe el archivo indicador.existente (). Elimínelo, luego lleve la aplicación a Front ().

EDIT : me gustaría saber por qué esto fue votado a la baja. Es la mejor solución que he visto hasta ahora. P.ej. el enfoque de socket del servidor falla si otra aplicación ya está escuchando el puerto.

Simplemente descargue Microsoft Windows Sysinternals TCPView (o use netstat) , comience, 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 nueva solución Socket (). En mi implementación, puedo producir fallas cada vez. Y es lógico , porque es la base misma del enfoque. O, ¿qué es lo que no consigo respecto a cómo implementar esto?

¡Por favor, infórmame si estoy equivocado al respecto y cómo me equivoco!

Mi opinión, que le pido que rebate si es posible, es que se recomienda 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 esta vista resulta correcta, entonces absolutamente no puede ser que una solución presentada que no tenga este problema sea degradada 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).
  • Se produce un error en el entorno de varios usuarios: 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é chanchullos estás haciendo cuando tu editor de texto reclama un socket de servidor.

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

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

Esto se puede cambiar fácilmente para simplemente crear y no eliminar el archivo, y luego se puede colocar información que la instancia adecuada puede evaluar, por ejemplo. los argumentos de la línea de comando - y la instancia adecuada pueden 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();
        }
    }

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top