Как убедиться, что запущен только один экземпляр приложения Java?

Я хочу, чтобы мое приложение проверяло, запущена ли уже другая версия самого себя.

Например, demo.jar запущен, пользователь щелкает, чтобы запустить его снова, но второй экземпляр понимает: «Ой, подождите, уже запущен demo.jar». и выходит с сообщением.


person Epicblood    schedule 09.05.2012    source источник


Ответы (11)


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

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

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

person Andrew White    schedule 09.05.2012
comment
просто добавление: я не пробовал использовать pid, который безопаснее, если в системе доступен PID. Я пробовал это раньше, но обнаружил небольшую проблему (просто ложное срабатывание). Теперь это может быть специфично для машины. Итак, когда моя программа закрывается, она удаляет файл. Файл не был удален сразу (из-за буферизации или чего-то еще), и когда я снова быстро запустил программу, она нашла файл (даже когда он был удален) - person Ankit; 09.05.2012
comment
Вероятно, вы захотите дать пользователю возможность удалить файл блокировки на случай, если приложение выйдет из строя и не удалит его автоматически. - person Tom; 09.05.2012
comment
подойдет, есть ли способ сделать так, чтобы файл автоматически удалялся при перезагрузке? - person Epicblood; 09.05.2012
comment
@JorisBolsens поместил файл в системный каталог tmp. Я знаю, что это очищается при перезагрузке для Linux, и я думаю, что для Windows есть аналогичное место. - person Andrew White; 09.05.2012
comment
хорошо, и есть ли способ сделать так, чтобы он удалялся, даже если мой jar выйдет из строя? - person Epicblood; 10.05.2012
comment
Лучше прислушаться к существующему экземпляру, поскольку файл может быть "взломан" или, что еще хуже, блокировка не может быть снята, если мягкий разрыв: / См. Ответы на тезисы: stackoverflow.com/a/920403/244911 - person LE GALL Benoît; 11.06.2015

Принудительное использование одного экземпляра программы, запущенной с помощью ServerSocket Lock

Код Java. Поместите это в файл с именем Main.java:

import java.net.*;
import java.io.*;
public class Main{
  public static void main(String args[]){
    ServerSocket socket = null;
    try {
      socket = new ServerSocket(34567);
      System.out.println("Doing hard work for 100 seconds");
      try{ Thread.sleep(100000); } catch(Exception e){ }
      socket.close();
    }
    catch (IOException ex) {
      System.out.println("App already running, exiting...");
    }
    finally {
      if (socket != null)
          try{ socket.close(); } catch(Exception e){}
    }
  }
}

Скомпилируйте и запустите

javac Main.java
java Main

Проверьте это в обычном случае:

Запустите программу. У вас есть 100 секунд, чтобы снова запустить программу в другом терминале, она провалится, сказав, что она уже запущена. Затем подождите 100 секунд, это должно позволить вам запустить его на втором терминале.

Протестируйте его после принудительной остановки программы с помощью kill -9

  1. Запустите программу в терминале 1.
  2. kill -9 этот процесс с другого терминала в течение 100 секунд.
  3. Запустить программу еще раз, запуск разрешен.

Вывод:

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

Недостатки

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

person Eric Leschinski    schedule 14.08.2014

Простое, но мощное протестированное решение.

    static File file;
    static FileChannel fileChannel;
    static FileLock lock;
    static boolean running = false;

    @SuppressWarnings("resource")
    public static boolean checkIfAlreadyRunning() throws IOException {
        file = new File(FilePath.FILEPATH + "az-client.lock");
        if (!file.exists()) {
            file.createNewFile();
            running = true;
        } else {
            file.delete();
        }

        fileChannel = new RandomAccessFile(file, "rw").getChannel();
        lock = fileChannel.tryLock();

        if (lock == null) {
            fileChannel.close();
            return true;
        }
        ShutdownHook shutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        return running;
    }

    public static void unlockFile() {
        try {
            if (lock != null)
                lock.release();
            fileChannel.close();
            file.delete();
            running = false;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class ShutdownHook extends Thread {
        public void run() {
            unlockFile();
        }
    }

Поместите эти методы в какой-нибудь класс Util и перед запуском вашего основного класса просто проверьте, что, если он уже существует, покажите пользователю какой-либо диалог, в противном случае запустите приложение. Он работает, даже если вы аварийно завершили Java-процесс или что-то еще. Он надежен и эффективен, не нужно настраивать прослушиватели DataGram или что-то еще ...

person AZ_    schedule 16.11.2013
comment
ваш FilePath.FILEPATH не из JDK, в моем проекте нет Jenkins. Этот оператор file = new File (FilePath.FILEPATH + az-client.lock); следует изменить, чтобы применить его к моему случаю. Подскажите, пожалуйста, как? - person 5YrsLaterDBA; 29.07.2014
comment
какие? Я не понял вопроса - person AZ_; 11.08.2014
comment
Просто любопытно - если файл существует (иначе ответвление от if (! File.exists ()), вы удаляете файл, а затем пытаетесь его заблокировать. Это работает нормально, если файл существует и заблокирован? Я немного нахожу логику странно (зачем удалять файл, если он заблокирован?) ... - person fencekicker; 02.09.2016
comment
Как насчет того, чтобы просто создать файл и постоянно проверять его: stackoverflow.com/a/27297037/1911652 - person Atul; 19.02.2020

Если вы используете Mutex, логически этот Mutex должен быть доступен с любой JVM, на которой выполняется копия «программы». В программировании на C это может быть выполнено через разделяемую память, но по умолчанию в Java этого нет.

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

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

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

Другие люди использовали функцию exec для запуска эквивалента списка процессов, но это немного сложно из-за возможности состояния гонки (два процесса одновременно проверяют и не видят друг друга).

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

person Edwin Buck    schedule 09.05.2012

Если ваше приложение работает в Windows, вы можете вызвать CreateMutex через JNI.

jboolean ret = FALSE;    
HANDLE hMutex = CreateMutex(NULL, FALSE, mutexName); 
ret = TRUE;    
if(WAIT_TIMEOUT == WaitForSingleObject(hMutex, 10))  
{    
    ret = FALSE;  
}
else if(GetLastError() != 0)  
{    
    ret = FALSE;  
}

Это возвращает true, если этот мьютекс больше никто не использует, иначе false. Вы можете указать «myApplication» в качестве имени мьютекса или «Global \ MyApplication», если хотите, чтобы ваш мьютекс совместно использовался всеми сеансами Windows.

Изменить: это не так сложно, как кажется :), и я считаю его чистым.

person Jerome    schedule 09.05.2012
comment
Я также хотел бы иметь возможность использовать на Mac и Linux, но это действительно помогает xD - person Epicblood; 09.05.2012

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

Настройки хранятся в реестре Windows в HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

import java.io.*;
import java.util.prefs.Preferences;
public class JavaApplication3 {
    public static void main(String[] args){
        if(isRunning()){
            System.out.println("Two instances of this program cannot " +
                    "be running at the same time.  Exiting now");
        }
        else{
            onStart();
            epicHeavyWorkGoesHere();
            onFinish();
        }
    }
    public static void epicHeavyWorkGoesHere(){
        try {
            Thread.sleep(15000);
        } catch (InterruptedException ex) {}
    }
    public static void onStart(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
        prefs.put("RUNNINGPID", getCurrentPID());
    }
    public static void onFinish(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
        prefs.put("RUNNINGPID", "");
    }
    public static boolean isRunning(){
        Preferences prefs = Preferences.systemRoot().node("JavaApplication3");

        if (prefs.get("RUNNINGPID", null) == null || prefs.get("RUNNINGPID", null).equals(""))
            return false;

        if (isProcessIdRunningOnWindows(Integer.parseInt(prefs.get("RUNNINGPID", null))))
            return true;
        return false;
    }
    public static String getCurrentPID(){
        //This function should work with Windows, Linux and Mac but you'll have to 
        //test to make sure.  If not then get a suitable getCurrentPID function replacement.
        try{
            java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
            java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
            jvm.setAccessible(true);
            sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
            java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
            pid_method.setAccessible(true);
            return pid_method.invoke(mgmt) + "";
        }
        catch(Exception e){
            throw new RuntimeException("Cannot get the current PID");
        }
    }
    public static boolean isProcessIdRunningOnWindows(int pid){
        //This Function only works for windows, if you want it to work on linux
        //or mac, you will have to go find a replacement method that 
        //takes the processID as a parameter and spits out a true/false 
        //if it is running on the operating system.
        try {
            Runtime runtime = Runtime.getRuntime();
            String cmds[] = {"cmd", "/c", "tasklist /FI \"PID eq " + pid + "\""};
            Process proc = runtime.exec(cmds);

            InputStream inputstream = proc.getInputStream();
            InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
            BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
            String line;
            while ((line = bufferedreader.readLine()) != null) {
                if (line.contains(" " + pid + " ")){
                    return true;
                }
            }
            return false;
        }
        catch (Exception ex) {
            throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
        }
    }
}

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

person Eric Leschinski    schedule 02.08.2013
comment
1) Как это узнать, зависает ли программа? Если предыдущая программа зависает, PID в реестре и процесс в списке задач по-прежнему остаются. Вы не могли отличить запущенный процесс от зависшего. 2) Я мог просто просканировать список задач, чтобы найти процесс (может быть запущен или завис). Чего еще можно достичь, сохранив PID в реестре? - person Lai; 08.08.2013
comment
Ах, хороший улов. Я хотел добавить еще один ключ, в котором хранится время последнего успешного завершения. Также сохраните переменную для максимального количества времени, разрешенного для запуска. Когда программа запускается, время выполнения PID может быть рассчитано и уничтожено, если оно работает слишком долго. - person Eric Leschinski; 08.08.2013

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

person user207421    schedule 16.11.2013

Следующее решение работает и в двух смертоносных сценариях. 1> Даже ваш запущенный exe запланирован как javaw.exe в диспетчере задач. 2> Вы можете установить свое приложение в двух местах, и после запуска в обоих местах оно также работает.

String tempDir = System.getProperty("java.io.tmpdir");// dependent to OS find any tem dir.
        String filePath = tempDir + "lockReserverd.txt";
        try {
            final File file = new File(filePath);

            if(file.exists())
                return false;

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

or

Это будет работать, если ваш application.exe указан в диспетчере задач.

"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
person Community    schedule 11.09.2014

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

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class SingleInstance {

    @SuppressWarnings("resource")
    public static boolean isAlreadyRunning() {
        File file;
        FileChannel fileChannel;
        File userDir = new File(System.getProperty("user.home"));
        file = new File(userDir, myLockName());

        if (!file.exists()) {
            try {
                file.createNewFile();
                file.deleteOnExit();
            } catch (IOException e) {
                throw new RuntimeException("Unable to create Single Instance lock file!", e);
            }
        }

        try {
            fileChannel = new RandomAccessFile(file, "rw").getChannel();
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Single Instance lock file vanished!", e);
        }
        try {
            if (fileChannel.tryLock() != null) {
                return false;
            }
        } catch (Exception e) {
        }
        try {
            fileChannel.close();
        } catch (IOException e1) {
        }
        return true;
    }

    private static String myLockName() {
        return "." + SingleInstance.class.getProtectionDomain().getCodeSource().getLocation().getPath()
                .replaceAll("[^a-zA-Z0-9_]", "_");
    }
}
person Michael Conrad    schedule 01.08.2016

Это также хорошее решение, если ваше приложение может быть запланировано в диспетчере задач с уникальным именем.

 "tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
person Community    schedule 11.09.2014

Проверьте PID и технику блокировки файлов

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

    static File file;
    static FileChannel fileChannel;
    static FileLock lock;
    static boolean running = false;
    static String currentPID = null;
    static String lockFilePID = null;
    public static final String USER_DIR = System.getProperty("user.dir");
    public static final String LOCK_FILE = "az-client.lock";

    public static boolean checkInstance() {
        try {
            file = new File(USER_DIR + File.separator + LOCK_FILE);
            currentPID = Integer.toString(getCurrentPID());
            if (!file.exists()) {
                file.createNewFile();
                writePID(currentPID);
                lockFile();
                addShudDownHook();
                running = true;
                return running;
            } else {
                if (isFileLocked()) {
                    syso("App already running");
                    System.exit(0);
                } else {
                    lockFilePID = getPIDFromLockFile();
                    if (isProcessIdRunningOnWindows(Integer.parseInt(lockFilePID))) {
                        lockFile();
                        addShudDownHook();
                        running = true;
                        return running;
                    } else {
                        file.delete();
                        file.createNewFile();
                        writePID(currentPID);
                        lockFile();
                        addShudDownHook();
                        running = true;
                        return running;
                    }
                }
            }
        } catch (Exception e) {
            syso(e + "App already running");
            System.exit(0);
        }
        return running;
    }

    /**
     * 
     * @return
     * @throws IOException
     */
    @SuppressWarnings("resource")
    private static boolean isFileLocked() throws IOException {
        fileChannel = new RandomAccessFile(file, "rw").getChannel();
        lock = fileChannel.tryLock();
        if (lock == null) {
            fileChannel.close();
            fileChannel = null;
            return true;
        } else {
            lock.release();
            fileChannel.close();
            fileChannel = null;
        }
        return false;
    }


    public static int getCurrentPID() {
        // This function should work with Windows, Linux and Mac but you'll have
        // to
        // test to make sure. If not then get a suitable getCurrentPID function
        // replacement.
        try {
            java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
            java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
            jvm.setAccessible(true);
            sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
            java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
            pid_method.setAccessible(true);
            return (int) pid_method.invoke(mgmt);
        } catch (Exception e) {
            throw new RuntimeException("Cannot get the current PID");
        }
    }

    public static boolean isProcessIdRunningOnWindows(int pid) {
        // This Function only works for windows, if you want it to work on linux
        // or mac, you will have to go find a replacement method that
        // takes the processID as a parameter and spits out a true/false
        // if it is running on the operating system.
        try {
            Runtime runtime = Runtime.getRuntime();
            String cmds[] = { "cmd", "/c", "tasklist /FI \"PID eq " + pid + "\"" };
            Process proc = runtime.exec(cmds);

            InputStream inputstream = proc.getInputStream();
            InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
            BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
            String line;
            while ((line = bufferedreader.readLine()) != null) {
                if (line.contains(" " + pid + " ")) {
                    return true;
                }
            }
            return false;
        } catch (Exception ex) {
            throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
        }
    }

    /**
     * This method write PID to Lock file
     * 
     * @param pid
     * @throws Exception
     */
    private static void writePID(String pid) throws Exception {
        try {
            // To Do write PID to LockFile
        } catch (Exception e) {
            syso(e);
            throw e;
        }
    }

    /**
     * This method return PID from Lock File
     * 
     * @return
     * @throws Exception
     */
    private static String getPIDFromLockFile() throws Exception {
        try {
            return //To Do getPID from File
        } catch (Exception e) {
            syso(e);
            throw e;
        }
    }

    private static void addShudDownHook() {
        try {
            ShutdownHook shutdownHook = new ShutdownHook();
            Runtime.getRuntime().addShutdownHook(shutdownHook);
        } catch (Exception e) {
            LogWriter.logger.error(e);
        }
    }

    private static void unlockFile() {
        try {
            if (lock != null) {
                lock.release();
            }
            fileChannel.close();
            file.delete();
            running = false;
        } catch (IOException e) {
            syso(e);
        }
    }

    private static void lockFile() {
        try {
            fileChannel = new RandomAccessFile(file, "rw").getChannel();
            lock = fileChannel.tryLock();
            if (lock == null) {
                fileChannel.close();
                fileChannel = null;
            }
        } catch (IOException e) {
            syso(e);
        }
    }

    static class ShutdownHook extends Thread {
        public void run() {
            unlockFile();
        }
    }
person Kunal    schedule 15.09.2014