Come implementare un'applicazione Java a istanza singola?


89

A volte vedo molte applicazioni come msn, Windows Media Player ecc. Che sono applicazioni a istanza singola (quando l'utente viene eseguito mentre l'applicazione è in esecuzione, non verrà creata una nuova istanza dell'applicazione).

In C #, uso Mutexclass per questo, ma non so come farlo in Java.


Un approccio molto semplice con Java NIO vedere completo esempio stackoverflow.com/a/20015771/185022
AZ_

Risposte:


62

Se credo a questo articolo , di:

avendo la prima istanza che tenta di aprire un socket in ascolto sull'interfaccia localhost. Se è in grado di aprire il socket, si presume che questa sia la prima istanza dell'applicazione da avviare. In caso contrario, si presume che un'istanza di questa applicazione sia già in esecuzione. La nuova istanza deve notificare all'istanza esistente che è stato tentato un avvio, quindi uscire. L'istanza esistente subentra dopo aver ricevuto la notifica e attiva un evento al listener che gestisce l'azione.

Nota: Ahe menziona nel commento che utilizza InetAddress.getLocalHost()può essere ingannevole:

  • non funziona come previsto nell'ambiente DHCP perché l'indirizzo restituito dipende dall'accesso alla rete del computer.
    La soluzione era aprire la connessione con InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Probabilmente correlato al bug 4435662 .
  • Ho anche trovato il bug 4665037 che riporta i risultati attesi di getLocalHost: indirizzo IP di ritorno della macchina, vs. risultati effettivi: ritorno 127.0.0.1.

è sorprendente avere un getLocalHostritorno 127.0.0.1su Linux ma non su Windows.


Oppure puoi usare ManagementFactoryobject. Come spiegato qui :

Il getMonitoredVMs(int processPid)metodo riceve come parametro il PID dell'applicazione corrente e cattura il nome dell'applicazione che viene chiamato dalla riga di comando, ad esempio, l'applicazione è stata avviata da c:\java\app\test.jarpath, quindi la variabile del valore è " c:\\java\\app\\test.jar". In questo modo, cattureremo solo il nome dell'applicazione sulla riga 17 del codice sottostante.
Dopodiché, cerchiamo in JVM un altro processo con lo stesso nome, se lo abbiamo trovato e il PID dell'applicazione è diverso, significa che è la seconda istanza dell'applicazione.

JNLP offre anche un file SingleInstanceListener


3
Tieni presente che la prima soluzione ha un bug. Abbiamo scoperto di recente che InetAddress.getLocalHost()non funziona come previsto nell'ambiente DHCP perché l'indirizzo restituito dipende dall'accesso alla rete del computer. La soluzione era aprire la connessione con InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe

2
@Ahe: ottimo punto. Ho incluso il tuo commento così come i riferimenti alle segnalazioni di bug Oracle-Sun nella mia risposta modificata.
VonC

3
Secondo JavaDoc InetAddress.getByName(null)restituisce l'indirizzo dell'interfaccia di loopback. Immagino che sia meglio specificare 127.0.0.1 manualmente perché in teoria dovrebbe funzionare anche in ambienti solo IPv6.
kayahr


1
@ Puce Certo, nessun problema: ho ripristinato quei collegamenti.
VonC

65

Uso il seguente metodo nel metodo principale. Questo è il metodo più semplice, più robusto e meno invadente che ho visto, quindi ho pensato di condividerlo.

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 è il parametro "lockFile" per un'applicazione desktop? il nome del file jar dell'applicazione? che ne dici se non ci sono file jar solo alcuni file di classe?
5YrsLater DBA

2
È davvero necessario rilasciare manualmente il blocco del file e chiudere il file all'arresto? Non accade automaticamente quando il processo muore?
Natix

5
Ma cosa succede se l'alimentazione si interrompe e il computer si spegne senza eseguire il gancio di arresto? Il file persisterà e l'applicazione non potrà essere cancellata.
Petr Hudeček

6
@ PetrHudeček Va tutto bene. Non importa come finisce l'applicazione, il blocco dei file verrà rilasciato. Se non si è trattato di un arresto corretto, questo ha anche il vantaggio di consentire all'applicazione di realizzarlo alla prossima esecuzione. In ogni caso: ciò che conta è il blocco, non la presenza del file stesso. Se il file è ancora lì, l'applicazione verrà avviata comunque.
Presidente Dreamspace

@Robert: grazie per la tua soluzione, da allora l'ho sempre usata. E proprio ora, l'ho esteso per comunicare anche all'istanza già esistente che un'altra istanza ha tentato di avviare, utilizzando la cartella WatchService! stackoverflow.com/a/36772436/3500521
Dreamspace President

9

Se l'app. ha una GUI, avviala con JWS e usa ilSingleInstanceService .

Aggiornare

Il plug-in Java (richiesto sia per applet che per app JWS) è stato deprecato da Oracle e rimosso dal JDK. I produttori di browser lo avevano già rimosso dai loro browser.

Quindi questa risposta è defunta. Lasciandolo qui solo per avvertire le persone che guardano la vecchia documentazione.


2
Si noti inoltre che sembra che l'istanza in esecuzione possa essere informata di nuove istanze e dei loro argomenti, facilitando la comunicazione in tale programma.
Thorbjørn Ravn Andersen

6

Sì, questa è una risposta davvero decente per l'applicazione a istanza singola eclipse RCP eclipse di seguito è il mio codice

in 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

Usiamo il blocco dei file per questo (prendi un blocco esclusivo su un file magico nella directory dei dati dell'app dell'utente), ma siamo principalmente interessati a impedire l'esecuzione di più istanze.

Se stai cercando di fare in modo che la seconda istanza passi gli argomenti della riga di comando, ecc ... alla prima istanza, l'utilizzo di una connessione socket su localhost ucciderà due piccioni con una fava. Algoritmo generale:

  • All'avvio, prova ad aprire il listener sulla porta XXXX su localhost
  • se fallisce, apri un writer su quella porta su localhost e invia la riga di comando args, quindi shutdown
  • altrimenti, ascolta sulla porta XXXXX su localhost. Quando ricevi gli argomenti della riga di comando, elaborali come se l'app fosse stata avviata con quella riga di comando.

5

Ho trovato una soluzione, una spiegazione un po 'da cartone animato, ma funziona ancora nella maggior parte dei casi. Usa il semplice vecchio file di blocco per creare cose, ma in una vista abbastanza diversa:

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

Penso che sarà di aiuto a chi ha un'impostazione firewall rigorosa.


Sì, è un buon modo in quanto il blocco verrebbe rilasciato se l'applicazione si blocca o giù di lì :)
LE GALL Benoît

5

Puoi usare la libreria JUnique. Fornisce supporto per l'esecuzione di applicazioni java a istanza singola ed è open source.

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

La libreria JUnique può essere utilizzata per impedire a un utente di eseguire contemporaneamente più istanze della stessa applicazione Java.

JUnique implementa blocchi e canali di comunicazione condivisi tra tutte le istanze JVM avviate dallo stesso utente.

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

Dietro le quinte, crea blocchi di file nella cartella% USER_DATA% /. Junique e crea un socket server su una porta casuale per ogni appId univoco che consente l'invio / ricezione di messaggi tra le applicazioni java.


Posso usarlo per prevenire più istanze di applicazioni Java in una rete? aka, solo un'istanza della mia applicazione è consentita all'interno della mia intera rete
Wuaner



2

Potresti provare a utilizzare l'API delle preferenze. È indipendente dalla piattaforma.


Mi piace questa idea poiché l'API è semplice, ma forse alcuni scanner antivirus non vorrebbero che cambiassi il registro in modo da avere problemi simili a quelli con l'utilizzo di RMI su sistemi con un firewall software ... non sono sicuro.
Cal

@Cal Ma lo stesso problema è con il cambio di file / blocco / ecc ... non credi?
Alex

2

Un modo più generico per limitare il numero di istanze su una singola macchina, o anche su un'intera rete, è usare un socket multicast.

L'utilizzo di un socket multicast consente di trasmettere un messaggio a qualsiasi numero di istanze dell'applicazione, alcune delle quali possono trovarsi su macchine fisicamente remote su una rete aziendale.

In questo modo puoi abilitare molti tipi di configurazioni, per controllare cose come

  • Una o più istanze per macchina
  • Una o più istanze per rete (ad es. Controllo delle installazioni su un sito client)

Il supporto multicast di Java è tramite il pacchetto java.net con MulticastSocket e DatagramSocket come strumenti principali.

Nota : MulticastSocket non garantisce la consegna di pacchetti di dati, quindi dovresti usare uno strumento costruito su socket multicast come JGroups . JGroups fa garanzia di consegna di tutti i dati. È un unico file jar, con un'API molto semplice.

JGroups è in circolazione da un po 'di tempo e ha alcuni utilizzi impressionanti nel settore, ad esempio è alla base del meccanismo di clustering di JBoss per trasmettere dati a tutte le istanze di un cluster.

Usare JGroups, limitare il numero di istanze di un'app (su una macchina o una rete, diciamo: al numero di licenze che un cliente ha acquistato) è concettualmente molto semplice:

  • All'avvio dell'applicazione, ogni istanza tenta di unirsi a un gruppo denominato, ad esempio "My Great App Group". Avrai configurato questo gruppo per consentire 0, 1 o N membri
  • Quando il numero di membri del gruppo è maggiore di quello che hai configurato per esso, la tua app dovrebbe rifiutarsi di avviarsi.

1

È possibile aprire un file mappato in memoria e quindi vedere se quel file è già APERTO. se è già aperto, puoi tornare da main.

Un altro modo è usare i file di blocco (pratica unix standard). Un altro modo è mettere qualcosa negli appunti all'avvio principale dopo aver verificato se qualcosa è già negli appunti.

Altrimenti, puoi aprire un socket in modalità di ascolto (ServerSocket). Prima prova a connetterti al socket hte; se non riesci a connetterti, apri un serverocket. se ti connetti, allora sai che un'altra istanza è già in esecuzione.

Quindi, praticamente qualsiasi risorsa di sistema può essere utilizzata per sapere che un'app è in esecuzione.

BR, ~ A


avete codice per una di queste idee? inoltre, cosa succede se voglio che se l'utente avvia una nuova istanza, chiuda tutte le precedenti?
sviluppatore Android

1

Ho usato i socket per questo e, a seconda se l'applicazione è sul lato client o lato server, il comportamento è leggermente diverso:

  • lato client: se esiste già un'istanza (non posso ascoltare su una porta specifica) passerò i parametri dell'applicazione ed uscirò (potresti voler eseguire alcune azioni nell'istanza precedente) altrimenti avvierò l'applicazione.
  • lato server: se esiste già un'istanza stamperò un messaggio e uscirò, in caso contrario avvierò l'applicazione.

1
public class SingleInstance {
    finale statico pubblico 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 statico privato = null;

    public static void main (String [] args) {
        provare {
            FileChannel lockChannel = new RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            provare {
                flk = lockChannel.tryLock ();
            } catch (Throwable t) {
                t.printStackTrace ();
            }
            if (flk == null ||! flk.isValid ()) {
                System.out.println ("è già in esecuzione, lascia un messaggio al pipe ed esci ...");
                FileChannel pipeChannel = null;
                provare {
                    pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb. input (0, (byte) 1);
                    bb.force ();
                } catch (Throwable t) {
                    t.printStackTrace ();
                } finalmente {
                    if (pipeChannel! = null) {
                        provare {
                            pipeChannel.close ();
                        } catch (Throwable t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // Non rilasciamo il blocco e chiudiamo il canale qui, 
            // che verrà eseguito dopo che l'applicazione si arresta in modo anomalo o si chiude normalmente. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

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

    public static void createAndShowGUI () {

        frame = nuovo 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 : invece di utilizzare questo approccio WatchService, è possibile utilizzare un semplice thread di timer di 1 secondo per verificare se indicatorFile.exists (). Eliminalo, quindi porta l'applicazione su Front ().

EDIT : mi piacerebbe sapere perché questo è stato svalutato. È la migliore soluzione che ho visto finora. Ad esempio, l'approccio al socket del server fallisce se un'altra applicazione è già in ascolto sulla porta.

Basta scaricare Microsoft Windows Sysinternals TCPView (o utilizzare netstat), avviarlo, ordinare per "Stato", cercare il blocco di riga che dice "LISTENING", sceglierne uno il cui indirizzo remoto dice il nome del computer, inserire quella porta nel nuovo Socket ()-soluzione. Nella mia implementazione, posso produrre fallimenti ogni volta. Ed è logico , perché è il vero fondamento dell'approccio. O cosa non ottengo riguardo a come implementarlo?

Per favore, informami se e come mi sbaglio!

La mia opinione - che vi chiedo di smentire se possibile - è che si consiglia agli sviluppatori di utilizzare un approccio nel codice di produzione che fallirà in almeno 1 di circa 60000 casi. E se questo punto di vista sembra essere corretta, allora può assolutamente non essere che una soluzione presentata, che non ha questo problema è downvoted e criticato per la sua quantità di codice.

Svantaggi dell'approccio socket a confronto:

  • Non riesce se viene scelto il biglietto della lotteria (numero di porta) sbagliato.
  • Non riesce in ambiente multiutente: solo un utente può eseguire l'applicazione allo stesso tempo. (Il mio approccio dovrebbe essere leggermente modificato per creare i file nell'albero degli utenti, ma è banale.)
  • Non riesce se le regole del firewall sono troppo rigide.
  • Fa sì che gli utenti sospetti (che ho incontrato in natura) si chiedano quali imbrogli stai facendo quando il tuo editor di testo sta rivendicando un socket del server.

Ho appena avuto una bella idea su come risolvere il problema di comunicazione Java da nuova istanza a istanza esistente in un modo che dovrebbe funzionare su ogni sistema. Quindi, ho organizzato questa lezione in circa due ore. Funziona come un fascino: D

Si basa sull'approccio di blocco dei file di Robert (anche in questa pagina), che ho usato da allora. Per indicare all'istanza già in esecuzione che un'altra istanza ha tentato di avviarsi (ma non l'ha fatto) ... viene creato un file e immediatamente eliminato e la prima istanza utilizza WatchService per rilevare la modifica del contenuto della cartella. Non riesco a credere che apparentemente questa sia un'idea nuova, visto quanto sia fondamentale il problema.

Questo può essere facilmente modificato per creare e non eliminare il file, quindi è possibile inserire informazioni che l'istanza corretta può valutare, ad esempio gli argomenti della riga di comando, e l'istanza appropriata può quindi eseguire l'eliminazione. Personalmente, avevo solo bisogno di sapere quando ripristinare la finestra della mia applicazione e inviarla in primo piano.

Esempio di utilizzo:

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

Ecco la classe:

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

}

Non sono necessarie centinaia di righe di codice per risolvere questo problema. new ServerSocket()con un blocco di cattura è abbastanza adeguato,
Marchese di Lorne

@EJP Ti riferisci alla risposta accettata o di cosa stai parlando? Ho cercato un bel po 'per una soluzione senza libreria extra per piattaforma x che non fallisce, ad esempio perché alcuni socket erano già occupati da un'applicazione diversa . Se c'è una soluzione a questo, specialmente super semplice come ti riferisci, allora vorrei saperlo.
Presidente Dreamspace

@EJP: voglio chiederti di nuovo 1) a quale soluzione banale ti riferisci che sei stato a penzoloni come una carota davanti alla mia testa, 2) nel caso in cui sia la soluzione socket la risposta accettata inizia con, se uno o più di si applicano i miei punti elenco "Svantaggi dell'approccio socket" e 3) se è così, perché nonostante questi difetti consiglieresti comunque quell'approccio a uno come il mio.
Presidente Dreamspace

@EJP: Il problema è che la tua voce ha un certo peso, come sicuramente saprai, ma tutte le prove che ho mi costringono a convincermi che il tuo consiglio qui sia sbagliato. Vedi, non insisto che la mia soluzione sia giusta e tutto il resto, ma sono una macchina basata sull'evidenza. Non vedi che la tua posizione ti dà la responsabilità nei confronti della comunità di riempire i pezzi mancanti del puzzle di questa comunicazione che hai iniziato?
Presidente di Dreamspace

@EJP: Dal momento che purtroppo non c'è stata alcuna reazione da parte tua, ecco cosa presumo come un fatto: la verità sulla soluzione del socket del server è che è davvero profondamente imperfetta , e la ragione per la maggior parte di coloro che l'hanno scelta potrebbe essere stata "Gli altri usa anche questo. ", oppure potrebbero essere stati ingannati nell'usarlo da persone irresponsabili. Mi immagino che una parte del motivo per cui non ci degnò con le spiegazioni richieste è che non si può capire che / il motivo per cui mai messo in discussione questo approccio, e non si vuole fare una dichiarazione pubblica che rivela questo.
Presidente Dreamspace

1

La libreria Unique4j può essere utilizzata per eseguire una singola istanza di un'applicazione Java e passare messaggi. Puoi vederlo su https://github.com/prat-man/unique4j . Supporta Java 1.6+.

Utilizza una combinazione di blocchi di file e blocchi di porte dinamici per rilevare e comunicare tra istanze con l'obiettivo principale di consentire l'esecuzione di una sola istanza.

Di seguito è riportato un semplice esempio dello stesso:

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

    }

}

Disclaimer: ho creato e mantengo la libreria Unique4j.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.