Jak zaimplementować pojedynczą aplikację Java?

89

Czasami widzę wiele aplikacji, takich jak msn, windows media player itp., Które są aplikacjami z pojedynczą instancją (gdy użytkownik wykonuje, gdy aplikacja jest uruchomiona, nowa instancja aplikacji nie zostanie utworzona).

W C # używam Mutexdo tego klasy, ale nie wiem, jak to zrobić w Javie.

Fuangwith S.
źródło
Bardzo proste podejście z java NIO zobacz pełny przykład stackoverflow.com/a/20015771/185022
AZ_

Odpowiedzi:

62

Jeśli wierzę w ten artykuł , przez:

próba otwarcia gniazda nasłuchującego w interfejsie hosta lokalnego przy pierwszej instancji. Jeśli jest w stanie otworzyć gniazdo, zakłada się, że jest to pierwsza uruchamiana instancja aplikacji. Jeśli nie, zakłada się, że instancja tej aplikacji jest już uruchomiona. Nowa instancja musi powiadomić istniejącą instancję o próbie uruchomienia, a następnie zakończyć. Istniejąca instancja przejmuje kontrolę po otrzymaniu powiadomienia i wyzwala zdarzenie do detektora obsługującego akcję.

Uwaga: Ahe wspomina w komentarzu, że używanie InetAddress.getLocalHost()może być trudne:

  • nie działa zgodnie z oczekiwaniami w środowisku DHCP, ponieważ zwracany adres zależy od tego, czy komputer ma dostęp do sieci.
    Rozwiązaniem było otwarcie połączenia z InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Prawdopodobnie związany z błędem 4435662 .
  • Znalazłem również błąd 4665037, który zgłasza oczekiwane wyniki getLocalHost: zwrot adresu IP maszyny, a rzeczywiste wyniki: powrót 127.0.0.1.

zaskakujące jest getLocalHostpowrót 127.0.0.1w systemie Linux, ale nie w systemie Windows.


Lub możesz użyć ManagementFactoryobiektu. Jak wyjaśniono tutaj :

getMonitoredVMs(int processPid)Sposób otrzymuje jako parametr bieżącej aplikacji PID i złapać nazwę aplikacji, która jest wywoływana z wiersza poleceń, na przykład, aplikacja rozpoczęto od c:\java\app\test.jarścieżki, to jest wartość zmiennej „ c:\\java\\app\\test.jar”. W ten sposób złapiemy tylko nazwę aplikacji w linii 17 poniższego kodu.
Następnie szukamy w JVM innego procesu o tej samej nazwie, jeśli go znaleźliśmy i PID aplikacji jest inny, oznacza to, że jest to druga instancja aplikacji.

JNLP oferuje również SingleInstanceListener

VonC
źródło
3
Pamiętaj, że pierwsze rozwiązanie ma błąd. Niedawno odkryliśmy, że InetAddress.getLocalHost()nie działa to zgodnie z oczekiwaniami w środowisku DHCP, ponieważ zwracany adres zależy od tego, czy komputer ma dostęp do sieci. Rozwiązaniem było otwarcie połączenia z InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe
2
@Ahe: doskonała uwaga. W zredagowanej odpowiedzi zamieściłem Twój komentarz, a także odniesienia do raportów o błędach Oracle-Sun.
VonC,
3
Zgodnie z JavaDoc InetAddress.getByName(null)zwraca adres interfejsu pętli zwrotnej. Wydaje mi się, że jest to lepsze niż ręczne określenie 127.0.0.1, ponieważ teoretycznie powinno to również działać w środowiskach obsługujących tylko IPv6.
kayahr
3
Zobacz także Korzystanie z usługi SingleInstanceService .
trashgod
1
@Puce Jasne, nie ma problemu: przywróciłem te linki.
VonC
65

W metodzie głównej używam następującej metody. To najprostsza, najsolidniejsza i najmniej inwazyjna metoda, jaką widziałem, więc pomyślałem, że się nią podzielę.

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
źródło
jaki powinien być parametr „lockFile” dla aplikacji komputerowej? nazwa pliku jar aplikacji? a może nie ma pliku jar, tylko niektóre pliki klas?
5YrsLaterDBA
2
Czy naprawdę konieczne jest ręczne zwolnienie blokady pliku i zamknięcie pliku przy zamykaniu? Czy nie dzieje się to automatycznie po zakończeniu procesu?
Natix,
5
Ale co się stanie, jeśli zaniknie zasilanie i komputer wyłączy się bez uruchomienia haka zamykającego? Plik będzie się utrzymywał, a aplikacji nie będzie można uruchomić.
Petr Hudeček,
6
@ PetrHudeček Wszystko w porządku. Bez względu na to, jak zakończy się aplikacja, blokada plików zostanie zwolniona. Jeśli nie było to prawidłowe zamknięcie, ma to nawet tę zaletę, że pozwala aplikacji zrealizować to podczas następnego uruchomienia. W każdym razie: Liczy się blokada, a nie obecność samego pliku. Jeśli plik nadal tam jest, aplikacja i tak się uruchomi.
Prezydent Dreamspace
@Robert: Dzięki za twoje rozwiązanie, używam go od tamtej pory. I właśnie teraz rozszerzyłem go, aby również komunikować się z już istniejącą instancją, którą próbowała uruchomić inna instancja - za pomocą folderu WatchService! stackoverflow.com/a/36772436/3500521
Prezes Dreamspace
10

Jeśli plik app. ma GUI, uruchom go za pomocą JWS i użyj SingleInstanceService.

Aktualizacja

Wtyczka Java (wymagana zarówno dla apletów, jak i aplikacji JWS) została wycofana przez firmę Oracle i usunięta z JDK. Producenci przeglądarek już usunęli go ze swoich przeglądarek.

Więc ta odpowiedź jest nieaktualna. Pozostawienie go tutaj tylko po to, aby ostrzec ludzi oglądających starą dokumentację.

Andrew Thompson
źródło
2
Należy również zauważyć, że wygląda na to, że działająca instancja może być informowana o nowych instancjach i ich argumentach, co ułatwia komunikację z takim programem.
Thorbjørn Ravn Andersen
6

Tak, to naprawdę przyzwoita odpowiedź dla aplikacji eclipse RCP eclipse pojedynczej instancji, poniżej jest mój kod

w 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
źródło
5

Używamy do tego blokowania plików (chwytamy wyłączną blokadę na magiczny plik w katalogu danych aplikacji użytkownika), ale jesteśmy przede wszystkim zainteresowani zapobieganiem uruchamianiu wielu instancji.

Jeśli chcesz, aby druga instancja przekazała argumenty wiersza poleceń itp. Do pierwszej instancji, to użycie połączenia przez gniazdo na hoście lokalnym będzie zabijać dwie pieczenie na jednym ogniu. Algorytm ogólny:

  • Podczas uruchamiania spróbuj otworzyć odbiornik na porcie XXXX na hoście lokalnym
  • jeśli zawiedzie, otwórz program zapisujący na tym porcie na hoście lokalnym i wyślij argumenty wiersza poleceń, a następnie zamknij
  • w przeciwnym razie nasłuchuj na porcie XXXXX na hoście lokalnym. Po odebraniu argumentów wiersza polecenia przetwórz je tak, jakby aplikacja została uruchomiona z tym wierszem poleceń.
Kevin Day
źródło
5

Znalazłem rozwiązanie, trochę kreskówkowe wyjaśnienie, ale w większości przypadków nadal działa. Używa zwykłego starego pliku blokady do tworzenia rzeczy, ale w zupełnie innym widoku:

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

Myślę, że będzie to pomocne dla osób ze ścisłym ustawieniem zapory.

Ikon
źródło
Tak, to dobry sposób, ponieważ blokada zostanie zwolniona, jeśli aplikacja ulegnie awarii :)
LE GALL Benoît
5

Możesz skorzystać z biblioteki JUnique. Zapewnia obsługę uruchamiania aplikacji Java z pojedynczą instancją i jest oprogramowaniem typu open source.

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

Biblioteki JUnique można użyć, aby uniemożliwić użytkownikowi jednoczesne uruchomienie większej liczby instancji tej samej aplikacji Java.

JUnique implementuje blokady i kanały komunikacji współdzielone między wszystkimi instancjami JVM uruchomionymi przez tego samego użytkownika.

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

Pod maską tworzy blokady plików w folderze% USER_DATA% /. Junique i tworzy gniazdo serwera na losowym porcie dla każdego unikalnego appId, które umożliwia wysyłanie / odbieranie wiadomości między aplikacjami Java.

kolobok
źródło
Czy mogę tego użyć, aby zapobiec wielu wystąpieniom aplikacji Java w sieci? aka, tylko jedno wystąpienie mojej aplikacji jest dozwolone w całej mojej sieci
Wuaner
4

W systemie Windows możesz użyć launch4j .

Jacek Szymański
źródło
2

Możesz spróbować użyć interfejsu API preferencji. Jest niezależny od platformy.

Javamann
źródło
Podoba mi się ten pomysł, ponieważ API jest proste, ale być może niektóre skanery antywirusowe nie chcą, abyś zmienił rejestr, więc masz podobne problemy, jak w przypadku korzystania z RMI w systemach z zaporą programową ... nie jestem pewien.
Cal
@Cal Ale ten sam problem dotyczy zmiany / blokowania plików / itp ... nie sądzisz?
Alex
2

Bardziej ogólnym sposobem ograniczenia liczby instancji na pojedynczym komputerze lub nawet w całej sieci jest użycie gniazda multiemisji.

Korzystanie z gniazda multiemisji umożliwia rozgłaszanie wiadomości do dowolnej liczby wystąpień aplikacji, z których niektóre mogą znajdować się na fizycznie zdalnych komputerach w sieci firmowej.

W ten sposób możesz włączyć wiele typów konfiguracji, aby kontrolować takie rzeczy jak

  • Jedno lub wiele wystąpień na komputer
  • Jedna lub wiele instancji na sieć (np. Kontrolowanie instalacji w witrynie klienta)

Obsługa multiemisji w Javie odbywa się za pośrednictwem pakietu java.net, przy czym głównymi narzędziami są MulticastSocket i DatagramSocket .

Uwaga : MulticastSocket nie gwarantuje dostarczenia pakietów danych, dlatego powinieneś używać narzędzia zbudowanego na gniazdach multiemisji, takich jak JGroups . JGroups robi dostawy Gwarancja wszystkich danych. Jest to pojedynczy plik jar z bardzo prostym interfejsem API.

JGroups istnieje już od jakiegoś czasu i ma imponujące zastosowania w przemyśle, na przykład stanowi podstawę mechanizmu klastrowania JBoss, który rozgłasza dane do wszystkich instancji klastra.

Aby użyć JGroups, aby ograniczyć liczbę wystąpień aplikacji (na komputerze lub w sieci, powiedzmy: do liczby licencji zakupionych przez klienta) jest koncepcyjnie bardzo prosta:

  • Po uruchomieniu aplikacji każda instancja próbuje dołączyć do nazwanej grupy, np. „Moja Great App Group”. Skonfigurujesz tę grupę tak, aby zezwalała na 0, 1 lub N członków
  • Gdy liczba członków grupy jest większa niż skonfigurowana dla niej ... Twoja aplikacja powinna odmówić uruchomienia.
johnm
źródło
1

Możesz otworzyć plik mapowany w pamięci, a następnie sprawdzić, czy ten plik jest już OTWARTY. jeśli jest już otwarta, możesz wrócić z main.

Innym sposobem jest użycie plików blokujących (standardowa praktyka uniksowa). Innym sposobem jest umieszczenie czegoś w schowku, gdy główny startuje po sprawdzeniu, czy coś jest już w schowku.

W przeciwnym razie możesz otworzyć gniazdo w trybie nasłuchiwania (ServerSocket). Najpierw spróbuj połączyć się z gniazdem hte; jeśli nie możesz się połączyć, otwórz serwer serverocket. jeśli się połączysz, wiesz, że działa już inna instancja.

Tak więc prawie każdy zasób systemowy może być użyty do sprawdzenia, czy aplikacja jest uruchomiona.

BR, ~ A

anjanb
źródło
czy masz kod dla któregoś z tych pomysłów? a co jeśli chcę, aby użytkownik uruchomił nową instancję, zamknął wszystkie poprzednie?
programista Androida
1

Użyłem do tego gniazd i w zależności od tego, czy aplikacja jest po stronie klienta, czy po stronie serwera, zachowanie jest nieco inne:

  • po stronie klienta: jeśli instancja już istnieje (nie mogę nasłuchiwać na określonym porcie) przekażę parametry aplikacji i wyjdę (możesz chcieć wykonać jakieś akcje w poprzedniej instancji), jeśli nie, uruchomię aplikację.
  • po stronie serwera: jeśli instancja już istnieje, wydrukuję komunikat i wyjdę, jeśli nie, uruchomię aplikację.
adrian.tarau
źródło
1
public class SingleInstance {
    public static final String LOCK = System.getProperty ("user.home") + File.separator + "test.lock";
    public static final String PIPE = System.getProperty ("user.home") + File.separator + "test.pipe";
    prywatna statyczna ramka JFrame = null;

    public static void main (String [] args) {
        próbować {
            FileChannel lockChannel = new RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            próbować {
                flk = lockChannel.tryLock ();
            } catch (Throwable t) {
                t.printStackTrace ();
            }
            if (flk == null ||! flk.isValid ()) {
                System.out.println ("już działa, zostawiam wiadomość do potoku i kończę ...");
                FileChannel pipeChannel = null;
                próbować {
                    pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put (0, (bajt) 1);
                    bb.force ();
                } catch (Throwable t) {
                    t.printStackTrace ();
                } Wreszcie {
                    if (pipeChannel! = null) {
                        próbować {
                            pipeChannel.close ();
                        } catch (Throwable t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // Tutaj nie zwalniamy blokady i nie zamykamy kanału, 
            // co zostanie zrobione po awarii lub normalnym zamknięciu aplikacji. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = null;
            próbować {
                pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    bajt b = bb.get (0);
                    if (b> 0) {
                        bb.put (0, (bajt) 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 ();
            } Wreszcie {
                if (pipeChannel! = null) {
                    próbować {
                        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 ("GŁÓWNE OKNO", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}

Jerzy
źródło
1

EDYCJA : Zamiast korzystać z tego podejścia WatchService, można użyć prostego 1-sekundowego wątku czasowego do sprawdzenia, czy parametr IndicatorFile.exists (). Usuń go, a następnie przenieś aplikację doFront ().

EDYCJA : Chciałbym wiedzieć, dlaczego ten głos został odrzucony. To najlepsze rozwiązanie, jakie do tej pory widziałem. Np. Podejście do gniazda serwera zawodzi, jeśli zdarzy się, że inna aplikacja już nasłuchuje na porcie.

Po prostu pobierz Microsoft Windows Sysinternals TCPView (lub użyj netstat), uruchom go, posortuj według „Stanu”, poszukaj bloku linii z napisem „LISTENING”, wybierz ten, którego zdalny adres zawiera nazwę twojego komputera, umieść ten port w nowym Socket ()-rozwiązanie. Wdrażając to, mogę za każdym razem spowodować porażkę. Jest to logiczne , ponieważ stanowi podstawę tego podejścia. Albo czego nie rozumiem, jeśli chodzi o to, jak to zaimplementować?

Proszę poinformuj mnie, jeśli i jak się mylę!

Mój pogląd - który proszę, abyście obalili, jeśli to możliwe - jest taki, że programiści powinni stosować podejście w kodzie produkcyjnym, które zawodzi w co najmniej 1 z około 60000 przypadków. A jeśli okaże się, że ten pogląd jest słuszny, to absolutnie nie może być tak, że przedstawione rozwiązanie, które nie ma tego problemu, zostało odrzucone i skrytykowane za ilość kodu.

Wady podejścia opartego na gniazdach w porównaniu:

  • Nie powiedzie się, jeśli zostanie wybrany zły bilet na loterię (numer portu).
  • Niepowodzenie w środowisku wielu użytkowników: tylko jeden użytkownik może uruchomić aplikację w tym samym czasie. (Moje podejście należałoby nieco zmienić, aby utworzyć plik (i) w drzewie użytkowników, ale to trywialne).
  • Zawodzi, jeśli reguły zapory są zbyt surowe.
  • Sprawia, że ​​podejrzani użytkownicy (których spotkałem na wolności) zastanawiają się, jakie sztuczki robisz, gdy twój edytor tekstu zajmuje gniazdo serwera.

Właśnie przyszedł mi do głowy niezły pomysł, jak rozwiązać problem komunikacji Java od nowej instancji do istniejącej instancji w sposób, który powinien działać na każdym systemie. Więc przygotowałem te zajęcia w około dwie godziny. Działa jak marzenie: D

Opiera się na podejściu Roberta do blokowania plików (również na tej stronie), którego używam od tamtej pory. Aby poinformować już działającą instancję, że inna instancja próbowała się uruchomić (ale tego nie zrobiła) ... plik jest tworzony i natychmiast usuwany, a pierwsza instancja używa WatchService do wykrycia zmiany zawartości tego folderu. Nie mogę uwierzyć, że najwyraźniej jest to nowy pomysł, biorąc pod uwagę, jak fundamentalny jest problem.

Można to łatwo zmienić, aby po prostu utworzyć, a nie usunąć plik, a następnie umieścić w nim informacje, które może ocenić właściwa instancja, np. Argumenty wiersza poleceń - a następnie właściwa instancja może przeprowadzić usunięcie. Osobiście potrzebowałem tylko wiedzieć, kiedy przywrócić okno mojej aplikacji i wysłać je na wierzch.

Przykładowe zastosowanie:

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

Oto klasa:

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

}
Prezydent Dreamspace
źródło
Nie potrzebujesz setek linii kodu, aby rozwiązać ten problem. new ServerSocket()z blokadą jest całkiem wystarczająca,
Markiz Lorne
@EJP Czy odnosisz się do zaakceptowanej odpowiedzi, czy o czym mówisz? Szukałem sporo rozwiązania na platformie x bez dodatkowej biblioteki, które nie zawodzi, np. Dlatego, że zdarzyło się, że jakieś gniazdo jest już zajęte przez inną aplikację. Jeśli istnieje na to rozwiązanie - szczególnie tak bardzo proste, jak pan o tym wspomina - to chciałbym się o tym dowiedzieć.
Prezydent Dreamspace
@EJP: Chciałbym zapytać ponownie 1) co trywialne rozwiązanie masz na myśli, że zostały zwisające jak marchewka z przodu głowy, 2) w przypadku jest to rozwiązanie gniazdo przyjętym odpowiedź zaczyna się, jeśli jedna lub więcej z moje wypunktowane punkty „Wady podejścia z gniazdem” mają zastosowanie, a 3) jeśli tak, to dlaczego pomimo tych niedociągnięć nadal zalecałbyś to podejście zamiast takiego, jak moje.
Prezes Dreamspace
@EJP: Problem polega na tym, że twój głos ma dość dużą wagę, o czym jesteś pewien, że jesteś pewien, ale wszystkie dowody, które posiadam, zmuszają mnie do przekonania, że ​​twoja rada jest błędna. Widzisz, nie twierdzę, że moje rozwiązanie jest słuszne i tak dalej, ale jestem maszyną opartą na dowodach. Czy nie widzisz, że Twoje stanowisko nakłada na społeczność odpowiedzialność za uzupełnienie brakujących elementów tej komunikacji, którą rozpocząłeś?
Prezes Dreamspace
@EJP: Ponieważ niestety nie było żadnej reakcji z Twojej strony, przyjmuję to jako fakt: Prawdą na temat rozwiązania opartego na gniazdach serwera jest to, że jest ono naprawdę głęboko wadliwe , a powodem, dla którego większość osób wybrała takie rozwiązanie, może być „Inni tego też używaj. ”lub mogli zostać oszukani i oszukani przez nieodpowiedzialnych ludzi. Wydaje mi się, że jednym z powodów, dla których nie zaszczyciłeś nas wymaganymi wyjaśnieniami, jest to, że nie możesz pojąć tego / dlaczego nigdy nie kwestionowałeś tego podejścia i nie chcesz wydać publicznego oświadczenia, które to ujawni.
Prezydent Dreamspace
1

Biblioteki Unique4j można używać do uruchamiania pojedynczej instancji aplikacji Java i przekazywania komunikatów. Możesz to zobaczyć na https://github.com/prat-man/unique4j . Obsługuje Java 1.6+.

Używa kombinacji blokad plików i dynamicznych blokad portów do wykrywania i komunikowania się między instancjami, a głównym celem jest umożliwienie działania tylko jednej instancji.

Oto prosty przykład tego samego:

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

    }

}

Zastrzeżenie: stworzyłem i utrzymuję bibliotekę Unique4j.

Pratanu Mandal
źródło
0

Napisałem dedykowaną bibliotekę dla tego https://sanyarnd.github.io/applocker

Opiera się na blokowaniu kanału plików, więc nie będzie blokować numeru portu ani aplikacji zakleszczenia w przypadku zaniku zasilania (kanał jest zwalniany po zakończeniu procesu).

Biblioteka jest lekka i ma płynny interfejs API.

Został zainspirowany http://www.sauronsoftware.it/projects/junique/ , ale zamiast tego opiera się na kanałach plików. Są też inne dodatkowe nowe funkcje.

Alexander Biryukov
źródło