Як реалізувати єдиний приклад Java-програми?


89

Іноді я бачу багато додатків, таких як msn, медіаплеєр Windows тощо, які є додатками для одного екземпляра (коли користувач виконує програму під час роботи, новий екземпляр програми не створюється).

У 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.

дивно мати getLocalHostповернення 127.0.0.1на Linux, але не на windows.


Або ви можете використовувати ManagementFactoryоб'єкт. Як пояснюється тут :

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

JNLP також пропонує SingleInstanceListener


3
Майте на увазі, що перше рішення має помилку. Нещодавно ми виявили, що InetAddress.getLocalHost()не працює належним чином у середовищі DHCP, оскільки повернена адреса залежить від того, чи має комп'ютер доступ до мережі. Рішенням було відкрити зв'язок з InetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe

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

@ Роберт: Дякую за ваше рішення, з тих пір я його використовую. І ось зараз я розширив його, щоб також зв’язати з уже існуючим екземпляром, який намагався запустити інший екземпляр - за допомогою папки WatchService! stackoverflow.com/a/36772436/3500521
Президент Dreamspace

9

Якщо додаток. має графічний інтерфейс, запустіть його за допомогою JWS і використовуйте SingleInstanceService.

Оновлення

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

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


2
Також зауважте, що схоже, що запущений екземпляр може бути проінформований про нові екземпляри та їх аргументи, що полегшує спілкування в такій програмі.
Thorbjørn Ravn Andersen

6

Так, це дійсно пристойна відповідь на додаток eclipse RCP eclipse для одного екземпляра нижче - це мій код

у застосунку.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 на localhost
  • якщо не вдається, відкрийте програму запису до цього порту на localhost і надішліть аргументи командного рядка, а потім вимкніть
  • в іншому випадку прослуховуйте порт XXXXX на localhost. Отримуючи аргументи командного рядка, обробляйте їх так, ніби додаток було запущено за допомогою цього командного рядка.

5

Я знайшов рішення, трохи мультяшне пояснення, але все ще працює в більшості випадків. Він використовує звичайний старий файл блокування, який створює речі, але в зовсім іншому вигляді:

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

Думаю, це допоможе тим, хто має сувору настройку брандмауера.


Так, це хороший спосіб, оскільки блокування буде випущено подією, якщо програма аварійно завершиться або близько того :)
LE GALL Бенуа

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 і створює сокет сервера на випадковий порт для кожного унікального ідентифікатора програми, що дозволяє надсилати / отримувати повідомлення між програмами 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

Ви можете спробувати скористатися API налаштувань. Це не залежить від платформи.


Мені ця ідея подобається, оскільки API простий, але, можливо, деяким антивірусним сканерам не сподобається, що ви змінюєте реєстр, тому у вас виникають подібні проблеми, як при використанні RMI в системах із програмним брандмауером ....
Cal

@Cal Але така ж проблема і зі зміною / блокуванням файлів тощо ... Вам не здається?
Alex

2

Більш загальним способом обмеження кількості екземплярів на одній машині або навіть у всій мережі є використання багатоадресного сокета.

Використання багатоадресного розетки дозволяє передавати повідомлення в будь-яку кількість екземплярів вашої програми, деякі з яких можуть бути на фізично віддалених машинах у корпоративній мережі.

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

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

Підтримка під LGPL в Java здійснюється через java.net пакет з MulticastSocket і DatagramSocket бути основні інструменти.

Примітка : MulticastSocket не гарантує доставку пакетів даних, тому вам слід використовувати інструмент, побудований поверх багатоадресних сокетів, такий як JGroups . JGroups робить гарантії доставки всіх даних. Це один файл jar, з дуже простим API.

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

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

  • Після запуску вашої програми кожен екземпляр намагається приєднатися до названої групи, наприклад "My Great App Group". Ви налаштуєте цю групу, щоб дозволити 0, 1 або N членів
  • Коли кількість членів групи перевищує те, що ви для неї налаштували .. ваш додаток повинен відмовити у запуску.

1

Ви можете відкрити файл, зіставлений із пам'яттю, а потім перевірити, чи цей файл уже ВІДКРИТО. якщо він уже відкритий, ви можете повернутися з головного.

Інші способи - це використання файлів блокування (стандартна практика Unix). Ще один спосіб - це помістити щось у буфер обміну, коли основний починається після перевірки, чи щось вже є в буфері обміну.

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

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

BR, ~ A


чи є у вас код будь-якої з цих ідей? також, що якщо я хочу, що якщо користувач запустить новий екземпляр, він закриє всі попередні?
розробник android

1

Я використовував для цього сокети, і залежно від того, чи додаток знаходиться на стороні клієнта або сервера, поведінка дещо відрізняється:

  • на стороні клієнта: якщо екземпляр вже існує (я не можу слухати на певному порту), я передаю параметри програми та вийду (можливо, ви захочете виконати деякі дії в попередньому екземплярі), якщо ні, я запустити програму.
  • на стороні сервера: якщо екземпляр уже існує, я надрукую повідомлення та вийду, якщо ні, то запустити програму.

1
публічний клас 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 = null;
                спробуй {
                    pipeChannel = new 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 = null;
            спробуй {
                pipeChannel = new RandomAccessFile (PIPE, "rw"). getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (true) {
                    байт b = bb.get (0);
                    якщо (b> 0) {
                        bb.put (0, (байт) 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 ();
            } нарешті {
                if (pipeChannel! = null) {
                    спробуй {
                        pipeChannel.close ();
                    } catch (Throwable t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } catch (Throwable t) {
            t.printStackTrace ();
        } 
    }

    відкрита статична порожнеча createAndShowGUI () {

        frame = new JFrame ();
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setSize (800, 650);
        frame.getContentPane (). add (new JLabel ("ОСНОВНЕ ВІКНО", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}


1

РЕДАГУВАТИ : Замість використання цього підходу WatchService можна використати простий 1-секундний потік таймера, щоб перевірити, чи існує показник File.exe (). Видаліть його, а потім перенесіть програму до Front ().

EDIT : Я хотів би знати, чому це було проти. Це найкраще рішення, яке я бачив досі. Наприклад, підхід до серверного сокету не вдається, якщо інша програма вже слухає порт.

Просто завантажте Microsoft Windows Sysinternals TCPView (або скористайтеся netstat), запустіть його, відсортуйте за «Стан», знайдіть блок рядків із написом «СЛУХАТИ», виберіть той, на віддаленій адресі якого вказано ім’я вашого комп’ютера, вставте цей порт у ваш новий Socket () -рішення. У своїй реалізації я можу щоразу спричиняти невдачі. І це логічно , бо це сама основа підходу. Або що я не отримую щодо того, як це реалізувати?

Будь ласка, повідомте мене, якщо і як я помиляюся з цього приводу!

На мою думку - яку я прошу вас спростувати, якщо це можливо - полягає в тому, що розробникам рекомендується використовувати підхід у виробничому коді, який не вдасться щонайменше в 1 із приблизно 60000 випадків. І якщо ця точка зору виявляється вірною, то абсолютно не може бути, що представлене рішення, яке не має цієї проблеми, буде проголосовано і критиковано за його обсяг коду.

Недоліки сокетного підходу в порівнянні:

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

Я просто мав гарну ідею щодо того, як вирішити проблему зв'язку нового Java-екземпляра з існуючим екземпляром 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();
        }
    }

}

Для вирішення цієї проблеми вам не потрібні сотні рядків коду. new ServerSocket()з блоком лову цілком адекватно,
маркіз Лорнський

@EJP Ви маєте на увазі прийняту відповідь чи про що ви говорите? Я досить довго шукав рішення без додаткової бібліотеки для x-платформи, яке не виходить з ладу, наприклад, тому що деякі сокети вже були зайняті іншим додатком. Якщо є рішення для цього - особливо таке просте, як ви маєте на увазі, - то я хотів би про це знати.
Президент 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.