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

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

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

12 ответов


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

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

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


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

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 секунд, это должно позволить вам запустить его во 2-м терминал.


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

    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 или что вы делаете. Он надежен и эффективен, нет необходимости настраивать прослушиватели дейтаграмм или что-то еще...


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

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

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

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

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

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


Если ваше приложение работает в 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.

Edit: это не так сложно, как кажется:) и я нахожу его чистым.


стратегия этого кода заключается в том, чтобы сохранить 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 убит, и программа повторно запускается.


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

``

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

``


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


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

 "tasklist /FI \"IMAGENAME eq "+MyApplication+".exe

следующее решение работает в двух смертельных scenerio тоже. 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

или

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

"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe

Проверьте 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();
        }
    }

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

смотрите на получение процесс:

http://www.itechp2pexchange.com/content/linux-unix-run-only-one-instance-script