Как реализовать одноэкземплярное Java-приложение?

89

Иногда я вижу много приложений, таких как msn, проигрыватель Windows Media и т. Д., Которые являются приложениями с одним экземпляром (когда пользователь запускается во время работы приложения, новый экземпляр приложения не создается).

В C # я использую Mutexдля этого класс, но не знаю, как это сделать в Java.

Фуангвит С.
источник
Очень простой подход с java NIO см. Полный пример stackoverflow.com/a/20015771/185022
AZ_

Ответы:

62

Если я верю этой статье , автор:

попытка первого экземпляра открыть прослушивающий сокет на интерфейсе localhost. Если он может открыть сокет, предполагается, что это первый экземпляр приложения, который будет запущен. В противном случае предполагается, что экземпляр этого приложения уже запущен. Новый экземпляр должен уведомить существующий экземпляр о попытке запуска, а затем выйти. Существующий экземпляр вступает во владение после получения уведомления и запускает событие для прослушивателя, который обрабатывает действие.

Примечание: Ахе упоминается в комментариях , что с помощью InetAddress.getLocalHost()может быть сложно:

  • он не работает должным образом в DHCP-среде, поскольку возвращаемый адрес зависит от того, имеет ли компьютер доступ к сети.
    Решением было открыть соединение с InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    Вероятно, связано с ошибкой 4435662 .
  • Я также обнаружил ошибку 4665037, которая сообщает, чем ожидаемые результаты getLocalHost: вернуть IP-адрес машины или фактические результаты: вернуть 127.0.0.1.

Удивительно, что Linux getLocalHostвернулся, 127.0.0.1но не Windows.


Или вы можете использовать ManagementFactoryобъект. Как объяснено здесь :

getMonitoredVMs(int processPid)Метод принимает в качестве параметра текущего приложения PID, и поймать имя приложения , который вызывается из командной строки, например, приложение было запущено из c:\java\app\test.jarпути, то значение переменной « c:\\java\\app\\test.jar». Таким образом, мы поймем только имя приложения в строке 17 кода ниже.
После этого мы ищем JVM для другого процесса с тем же именем, если мы его нашли и PID приложения отличается, это означает, что это второй экземпляр приложения.

JNLP также предлагает SingleInstanceListener

VonC
источник
3
Имейте в виду, что первое решение содержит ошибку. Недавно мы обнаружили, что InetAddress.getLocalHost()это не работает должным образом в DHCP-среде, поскольку возвращаемый адрес зависит от того, имеет ли компьютер доступ к сети. Решением было открыть соединение с InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ахе
2
@Ahe: отличный момент. Я включил ваш комментарий, а также ссылки на отчет об ошибке Oracle-Sun в свой отредактированный ответ.
VonC
3
Согласно JavaDoc InetAddress.getByName(null)возвращает адрес интерфейса обратной петли. Я думаю, это лучше, чем указывать 127.0.0.1 вручную, потому что теоретически это также должно работать в средах только с IPv6.
kayahr
1
@Puce Конечно, без проблем: я восстановил эти ссылки.
VonC
65

В основном методе я использую следующий метод. Это самый простой, самый надежный и наименее навязчивый метод, который я видел, поэтому я подумал, что поделюсь им.

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;
}
Роберт
источник
какой параметр lockFile должен быть для настольного приложения? имя файла jar приложения? как насчет того, что нет файла jar только некоторые файлы классов?
5YrsLaterDBA
2
Действительно ли необходимо вручную снимать блокировку файла и закрывать файл при выключении? Разве это не происходит автоматически, когда процесс умирает?
Natix
5
Но что произойдет, если питание отключится и компьютер выключится без запуска обработчика выключения? Файл сохранится, и приложение будет недоступно.
Петр Худечек
6
@ PetrHudeček Все в порядке. Независимо от того, как приложение закроется, блокировка файла будет снята. Если это не было надлежащим завершением работы, то это даже дает возможность приложению реализовать это при следующем запуске. В любом случае: важна блокировка, а не наличие самого файла. Если файл все еще существует, приложение все равно запустится.
Президент Dreamspace
@Robert: Спасибо за ваше решение, с тех пор я им пользуюсь. И только сейчас я расширил его, чтобы также сообщить уже существующему экземпляру, что другой экземпляр пытался запустить - используя папку WatchService! stackoverflow.com/a/36772436/3500521
Президент Dreamspace
10

Если файл app. имеет графический интерфейс, запустите его с помощью JWS и используйте SingleInstanceService.

Обновить

Подключаемый модуль Java (необходимый как для апплетов, так и для приложений JWS) объявлен Oracle устаревшим и удален из JDK. Производители браузеров уже удалили его из своих браузеров.

Итак, этот ответ больше не существует. Оставляем его здесь только для предупреждения людей, просматривающих старую документацию.

Эндрю Томпсон
источник
2
Также обратите внимание, что похоже, что запущенный экземпляр может быть проинформирован о новых экземплярах и их аргументах, что упрощает взаимодействие с такой программой.
Thorbjørn Ravn Andersen
6

Да, это действительно достойный ответ для приложения с одним экземпляром eclipse RCP eclipse, ниже - мой код

в 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

Мы используем для этого блокировку файлов (захватываем эксклюзивную блокировку волшебного файла в каталоге данных приложения пользователя), но в первую очередь мы заинтересованы в предотвращении запуска нескольких экземпляров.

Если вы пытаетесь передать аргументы командной строки второму экземпляру и т. Д. Первому экземпляру, то использование сокет-соединения на localhost убьет двух зайцев одним выстрелом. Общий алгоритм:

  • При запуске попробуйте открыть прослушиватель на порту XXXX на локальном хосте
  • в случае неудачи откройте писатель для этого порта на локальном хосте и отправьте аргументы командной строки, затем выключите
  • в противном случае прослушивайте порт XXXXX на локальном хосте. При получении аргументов командной строки обрабатывайте их, как если бы приложение было запущено с этой командной строкой.
Кевин Дэй
источник
5

Я нашел решение, немного мультяшное объяснение, но в большинстве случаев оно работает. Он использует простой старый файл блокировки, но в совершенно другом виде:

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

Думаю, это поможет тем, у кого строгие настройки брандмауэра.

Иконка
источник
Да, это хороший способ, поскольку блокировка будет
снята,
5

Вы можете использовать библиотеку JUnique. Он обеспечивает поддержку запуска java-приложения с одним экземпляром и является открытым исходным кодом.

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

Библиотека JUnique может использоваться для предотвращения одновременного запуска пользователем нескольких экземпляров одного и того же приложения Java.

JUnique реализует блокировки и каналы связи, общие для всех экземпляров JVM, запущенных одним и тем же пользователем.

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

Под капотом он создает блокировки файлов в папке% USER_DATA% /. Junique и создает серверный сокет на случайном порте для каждого уникального appId, который позволяет отправлять / получать сообщения между приложениями Java.

колобок
источник
Могу ли я использовать это для предотвращения использования нескольких экземпляров Java-приложения в сети? иначе, только один экземпляр моего приложения разрешен во всей моей сети
Wuaner
2

ManagementFactory класс поддерживается в J2SE 5.0 или более поздней версии подробно

но теперь я использую J2SE 1.4, и я нашел этот http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/, но я никогда не тестировал. Что вы думаете об этом?

Фуангвит С.
источник
Я считаю, что это, по сути, то, что описано в первой ссылке моего ответа выше ... rbgrn.net/blog/2008/05/java-single-application-instance.html
VonC
2

Вы можете попробовать использовать Preferences API. Он не зависит от платформы.

Javamann
источник
Мне нравится эта идея, поскольку API прост, но, возможно, некоторые антивирусные сканеры не захотят, чтобы вы меняли реестр, поэтому у вас возникнут проблемы, аналогичные тем, которые возникают при использовании RMI в системах с программным брандмауэром… не уверен.
Cal,
@Cal Но та же проблема связана с изменением / блокировкой файлов и т. Д., Как вы думаете?
Alex
2

Более общий способ ограничения количества экземпляров на одной машине или даже во всей сети - это использование многоадресного сокета.

Использование многоадресного сокета позволяет вам транслировать сообщение любому количеству экземпляров вашего приложения, некоторые из которых могут находиться на физически удаленных машинах в корпоративной сети.

Таким образом, вы можете включить множество типов конфигураций, чтобы управлять такими вещами, как

  • Один или несколько экземпляров на машину
  • Один или несколько экземпляров в сети (например, контроль установок на клиентском сайте)

Поддержка многоадресной рассылки в Java осуществляется через пакет java.net, в котором MulticastSocket и DatagramSocket являются основными инструментами.

Примечание : MulticastSocket не гарантирует доставку пакетов данных, поэтому вам следует использовать инструмент, построенный на основе многоадресных сокетов, например JGroups . JGroups делает гарантии доставки всех данных. Это один-единственный файл jar с очень простым API.

JGroups существует некоторое время и имеет несколько впечатляющих применений в промышленности, например, он поддерживает механизм кластеризации JBoss, транслирующий данные всем экземплярам кластера.

Чтобы использовать JGroups, ограничить количество экземпляров приложения (скажем, на машине или в сети: количество лицензий, купленных клиентом) концептуально очень просто:

  • При запуске вашего приложения каждый экземпляр пытается присоединиться к названной группе, например «Моя отличная группа приложений». Вы настроите эту группу, чтобы разрешить 0, 1 или N участников
  • Когда количество участников группы превышает то, что вы для него настроили ... ваше приложение не должно запускаться.
Джонм
источник
1

Вы можете открыть файл с отображением памяти, а затем посмотреть, ОТКРЫТ ли этот файл. если он уже открыт, вы можете вернуться из main.

Другой способ - использовать файлы блокировки (стандартная практика unix). Еще один способ - поместить что-то в буфер обмена при запуске main после проверки того, что что-то уже находится в буфере обмена.

В противном случае вы можете открыть сокет в режиме прослушивания (ServerSocket). Сначала попробуйте подключиться к разъему hte; если вы не можете подключиться, откройте серверный сокет. если вы подключаетесь, то знаете, что другой экземпляр уже запущен.

Таким образом, практически любой системный ресурс можно использовать для определения того, что приложение запущено.

BR, ~ A

Анжанб
источник
у вас есть код для любой из этих идей? Кроме того, что, если я хочу, чтобы если пользователь запустил новый экземпляр, он закрывал все предыдущие?
разработчик Android
1

Я использовал для этого сокеты, и в зависимости от того, находится ли приложение на стороне клиента или на стороне сервера, поведение немного отличается:

  • на стороне клиента: если экземпляр уже существует (я не могу прослушивать определенный порт), я передам параметры приложения и выйду (вы можете выполнить некоторые действия в предыдущем экземпляре), если нет, я запущу приложение.
  • на стороне сервера: если экземпляр уже существует, я напечатаю сообщение и выйду, если нет, я запущу приложение.
adrian.tarau
источник
1
public class SingleInstance {
    общедоступная статическая финальная строка LOCK = System.getProperty ("user.home") + File.separator + "test.lock";
    общедоступная статическая финальная строка PIPE = System.getProperty ("user.home") + File.separator + "test.pipe";
    частный статический кадр JFrame = null;

    public static void main (String [] args) {
        пытаться {
            FileChannel lockChannel = новый RandomAccessFile (LOCK, "rw"). GetChannel ();
            FileLock flk = null; 
            пытаться {
                flk = lockChannel.tryLock ();
            } catch (Throwable t) {
                t.printStackTrace ();
            }
            if (flk == null ||! flk.isValid ()) {
                System.out.println ("все работает, сообщение отправлено по конвейеру и завершается ...");
                FileChannel pipeChannel = ноль;
                пытаться {
                    pipeChannel = новый RandomAccessFile (PIPE, «rw»). getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put (0, (байт) 1);
                    bb.force ();
                } catch (Throwable t) {
                    t.printStackTrace ();
                } наконец-то {
                    if (pipeChannel! = null) {
                        пытаться {
                            pipeChannel.close ();
                        } catch (Throwable t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // Здесь мы не снимаем блокировку и закрываем канал, 
            // что будет сделано после того, как приложение выйдет из строя или нормально закроется. 
            SwingUtilities.invokeLater (
                new Runnable () {
                    public void run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = ноль;
            пытаться {
                pipeChannel = новый RandomAccessFile (PIPE, «rw»). getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    байт b = bb.get (0);
                    if (b> 0) {
                        bb.put (0, (байт) 0);
                        bb.force ();
                        SwingUtilities.invokeLater (
                            new Runnable () {
                                public void run () {
                                    frame.setExtendedState (JFrame.NORMAL);
                                    frame.setAlwaysOnTop (истина);
                                    frame.toFront ();
                                    frame.setAlwaysOnTop (ложь);
                                }
                            }
                        );
                    }
                    Thread.sleep (1000);
                }
            } catch (Throwable t) {
                t.printStackTrace ();
            } наконец-то {
                if (pipeChannel! = null) {
                    пытаться {
                        pipeChannel.close ();
                    } catch (Throwable t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } catch (Throwable t) {
            t.printStackTrace ();
        } 
    }

    public static void createAndShowGUI () {

        кадр = новый JFrame ();
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setSize (800, 650);
        frame.getContentPane (). add (new JLabel ("ГЛАВНОЕ ОКНО", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (ноль);
        frame.setVisible (правда);
    }
}

Джордж
источник
1

РЕДАКТИРОВАТЬ : вместо использования этого подхода WatchService можно использовать простой поток 1-секундного таймера для проверки наличия indicatorFile.exists (). Удалите его, затем перенесите приложение в Front ().

РЕДАКТИРОВАТЬ : Я хотел бы знать, почему это было отклонено. Это лучшее решение, которое я когда-либо видел. Например, подход серверного сокета не работает, если другое приложение уже прослушивает порт.

Просто загрузите Microsoft Windows Sysinternals TCPView (или используйте netstat), запустите его, отсортируйте по «State», найдите строковый блок с надписью «LISTENING», выберите тот, чей удаленный адрес соответствует имени вашего компьютера, поместите этот порт в новый Socket ()-решение. В своей реализации я могу каждый раз давать сбой. И это логично , потому что это сама основа подхода. Или что я не понимаю, как это реализовать?

Пожалуйста, сообщите мне, если и чем я ошибаюсь по этому поводу!

Мое мнение - которое я прошу вас опровергнуть, если возможно, - заключается в том, что разработчикам рекомендуется использовать в производственном коде подход, который даст сбой по крайней мере в 1 из примерно 60000 случаев. И если эта точка зрения окажется правильной, то совершенно не может быть, что представленное решение, не имеющее этой проблемы, не получило голосов и критиковалось за объем кода.

Недостатки сокетного подхода в сравнении:

  • Не работает, если выбран неправильный лотерейный билет (номер порта).
  • Сбои в многопользовательской среде: только один пользователь может запускать приложение одновременно. (Мой подход нужно было бы немного изменить, чтобы создать файл (ы) в дереве пользователя, но это тривиально.)
  • Не работает, если правила брандмауэра слишком строгие.
  • Заставляет подозрительных пользователей (которых я встречал в дикой природе) задуматься, какие махинации вы затеваете, когда ваш текстовый редактор требует серверного сокета.

У меня просто возникла хорошая идея, как решить проблему связи Java между новым экземпляром и существующим таким образом, чтобы это работало в любой системе. Итак, я начал этот урок примерно за два часа. Работает как шарм: D

Он основан на подходе Роберта к блокировке файлов (также на этой странице), который я использовал с тех пор. Чтобы сообщить уже запущенному экземпляру, что другой экземпляр пытался запустить (но не сделал этого) ... файл создается и немедленно удаляется, и первый экземпляр использует WatchService для обнаружения изменения содержимого этой папки. Я не могу поверить, что это новая идея, учитывая, насколько фундаментальна проблема.

Это можно легко изменить, чтобы просто создать, а не удалить файл, а затем в него можно поместить информацию, которую может оценить соответствующий экземпляр, например, аргументы командной строки - и соответствующий экземпляр может затем выполнить удаление. Лично мне нужно было только знать, когда восстановить окно моего приложения и отправить его на передний план.

Пример использования:

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

Вот класс:

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

}
Президент Dreamspace
источник
Для решения этой проблемы не нужны сотни строк кода. new ServerSocket()с блокировкой вполне достаточно,
Marquis of Lorne
@EJP Вы имеете в виду принятый ответ или о чем говорите? Я довольно много искал решение x-platform без дополнительной библиотеки, которое не дает сбоев, например, потому что какой-то сокет уже был занят другим приложением. Если есть решение этой проблемы - особенно такое супер простое, как вы имеете в виду, - то я хотел бы знать об этом.
Президент Dreamspace
@EJP: я хочу спросить еще раз: 1) какое тривиальное решение вы имеете в виду, когда вы болтались, как морковь, перед моей головой, 2) если это решение сокета, с которого начинается принятый ответ, если один или несколько из Применяются мои пункты списка «Недостатки подхода с использованием сокетов» и 3) если да, то почему, несмотря на эти недостатки, вы все равно рекомендуете этот подход, а не такой, как мой.
Президент Dreamspace
@EJP: Проблема в том, что ваш голос имеет определенный вес, как вы наверняка знаете, но все доказательства, которые у меня есть, заставляют меня убеждать, что ваш совет здесь неверен. Видите ли, я не настаиваю на том, что мое решение верное и все такое, но я машина, основанная на доказательствах. Разве вы не видите, что ваша должность возлагает на вас ответственность перед сообществом заполнить недостающие кусочки головоломки в этом общении, которое вы начали?
Президент Dreamspace
@EJP: Поскольку, к сожалению, от вас не последовало никакой реакции, вот то, что я буду считать фактом: правда о решении для серверных сокетов заключается в том, что оно действительно глубоко ошибочно , и причиной того, что большинство из тех, кто выбрал его, могло быть: «Остальные используйте это тоже. ", иначе они могли быть обмануты, чтобы использовать это безответственные люди. Я предполагаю, что одна из причин, по которой вы не удостоили нас необходимыми объяснениями, заключается в том, что вы не можете понять этого / почему вы никогда не подвергали сомнению этот подход, и вы не хотите делать публичное заявление, раскрывающее это.
Президент Dreamspace
1

Библиотеку Unique4j можно использовать для запуска одного экземпляра приложения Java и передачи сообщений. Вы можете увидеть это на https://github.com/prat-man/unique4j . Он поддерживает Java 1.6+.

Он использует комбинацию блокировок файлов и динамических блокировок портов для обнаружения экземпляров и обмена данными между ними с основной целью - разрешить запуск только одного экземпляра.

Ниже приводится простой пример того же:

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

    }

}

Отказ от ответственности: я создал и поддерживаю библиотеку Unique4j.

Пратану Мандал
источник
0

Я написал для этого специальную библиотеку https://sanyarnd.github.io/applocker

Он основан на блокировке файлового канала, поэтому он не будет блокировать номер порта или тупиковое приложение в случае отключения электроэнергии (канал освобождается после завершения процесса).

Библиотека сама по себе легкая и имеет свободный API.

Он был вдохновлен http://www.sauronsoftware.it/projects/junique/ , но вместо этого основан на файловых каналах. И есть другие дополнительные новые функции.

Александр Бирюков
источник