ezhov-da / java-Запуск одного экземпляра приложения
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
[code:]java[:code] |
import java.io.File; |
import java.io.FileNotFoundException; |
import java.io.FileOutputStream; |
import java.io.IOException; |
import java.nio.channels.FileLock; |
import java.util.logging.Level; |
import java.util.logging.Logger; |
import javax.swing.JOptionPane; |
/** |
* Контроль открытия одного экземпляра приложения |
* |
* @author ezhov_da |
*/ |
public class ControleOneCopyApplication |
private static final Logger LOG = Logger.getLogger(ControleOneCopyApplication.class.getName()); |
public static void check(File file) throws FileNotFoundException |
LOG.info(«Проверка открытого экземпляра приложения «); |
if (!lock(file)) |
LOG.log(Level.INFO, «Приложение уже открыто»); |
JOptionPane.showMessageDialog(null, «Экземпляр приложения уже запущен.», «Внимание», JOptionPane.INFORMATION_MESSAGE); |
System.exit(1); |
> |
> |
private static boolean lock(File lockFile) throws FileNotFoundException |
if (lockFile == null) |
throw new IllegalArgumentException(«path don’t be null.»); |
> |
try |
final FileLock lock = new FileOutputStream(lockFile).getChannel().tryLock(); |
if (lock != null) |
Thread thread = new Thread(new Runnable() |
@Override |
public void run() |
while (true) |
try |
if (lock.isValid()) |
Thread.sleep(Long.MAX_VALUE); |
> |
> catch (InterruptedException ex) |
> |
> |
> |
>); |
thread.setDaemon(true); |
thread.start(); |
> |
LOG.log(Level.INFO, «Проверка открытого экземпляра приложения: «, (lock != null)); |
return lock != null; |
> catch (IOException ex) |
LOG.log(Level.SEVERE, «Ошибка проверки наличия открытого экземпляра класса «, ex); |
> |
return true; |
> |
> |
[/code] |
Как убедиться, что запущен только один экземпляр приложения Java?
Я хочу, чтобы мое приложение проверяло, уже ли запущена другая версия. Например, demo.jar запущен, пользователь нажимает кнопку, чтобы запустить его снова, но второй экземпляр реализует «oh wait, уже работает demo.jar «. и завершает работу с сообщением.
12 ответов
То, что вы ищете, возможно, лучше всего сделать с помощью файла блокировки. В файле блокировки я просто имею в виду файл, который будет иметь предопределенное местоположение и существование которого является вашим мьютексом. Проверьте, существует ли этот файл при запуске вашей программы, если это произойдет, немедленно выйдите. Создайте файл в известном месте. Если ваша программа завершена нормально, удалите файл блокировки. Наверное, лучше всего, если вы также можете заполнить файл с помощью pid (идентификатор процесса), чтобы вы могли обнаруживать аномальные выходы, которые не удаляли файл, но это зависит от конкретной ОС.
просто дополнение: я не пробовал pid, который безопаснее, если в системе есть PID. Я пробовал это ранее, но обнаружил небольшую проблему (просто ложно-положительный). Теперь это может быть связано с конкретной машиной. Поэтому, когда моя программа закрывается, она удаляет файл. Файл не удалялся сразу (из-за буферизации или чего-то еще), и когда я снова быстро запустил программу, он нашел бы файл (даже если он был удален)
Возможно, вы захотите дать пользователю способ удалить файл блокировки, на случай, если приложение вылетит и не удалит его автоматически.
@JorisBolsens поместил файл в системный каталог tmp. Я знаю, что это очищает при перезагрузке для Linux, и я думаю, что есть аналогичное расположение для Windows.
хорошо, и есть ли в любом случае, что я могу сделать так, чтобы он удалял, даже если моя банка терпит крах?
Лучше прислушиваться к существующему экземпляру, так как файл может вызвать «взлом», или, что еще хуже, блокировка не может быть снята, если мягкий разрыв: / См. Ответы на вопросы: stackoverflow.com/a/920403/244911
Принудительно использовать один экземпляр программы, работающей с блокировкой 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)<> > > >
Скомпилировать и запустить
Протестируйте его в обычном случае:
Запустите программу. У вас есть 100 секунд, чтобы снова запустить программу в другом терминале, и она провалится, сказав, что она уже работает. Затем подождите 100 секунд, это должно позволить вам запустить его во 2-м терминале.
Протестируйте его после принудительной остановки программы с помощью kill -9
- Запустите программу в терминале 1.
- kill -9, который обрабатывается с другого терминала в течение 100 секунд.
- Запустите программу еще раз, ее можно запустить.
Ротация сокета очищается операционной системой, когда ваша программа больше не работает. Поэтому вы можете быть уверены, что программа не будет работать дважды.
Если какой-то подлый человек или какой-то непослушный процесс должны были связывать все порты или только ваш порт, то ваша программа не будет работать, потому что она думает, что она уже работает.
Простое, но мощное протестированное решение.
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 или что-то еще.
Ваш FilePath.FILEPATH не из JDK, у меня нет Jenkins в моем проекте. Этот оператор file = new File (FilePath.FILEPATH + «az-client.lock»); должен быть изменен для применения в моем случае. Не могли бы вы сказать мне, как?
Просто любопытно — если файл существует (иначе ответвьте от if (! File.exists ()), вы удаляете файл, а затем пытаетесь заблокировать его. Работает ли это нормально, если файл существует и заблокирован? Я немного нахожу логику странно (зачем удалять файл, если он заблокирован?) .
Стратегия этого кода заключается в том, чтобы поддерживать PID от последнего запуска в реестре, если этот PID найден в системе, не запускайте его. Если вы закончите, reset.
Настройки сохраняются в реестре 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[] = ; 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 будет убит, а программа заново запустится.
1) Как это узнать, если программа зависает? Если предыдущая программа зависает, PID в реестре и процесс в списке задач по-прежнему остаются. Вы не могли различить запущенный процесс или зависший процесс. 2) Я мог бы просто просканировать список задач, чтобы найти процесс (может быть запущен или завис). Чего еще можно достичь, сохранив PID в реестре?
Ах, хороший улов. Я хотел добавить еще один ключ, который хранит время последнего успешного завершения. Также сохраняйте переменную для «максимального количества времени, которое можно запустить». Когда программа запускается, время выполнения PID может быть рассчитано и уничтожено, если оно выполняется слишком долго.
Если ваше приложение работает в 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)
Это возвращает true, если никто не использует этот мьютекс, иначе false. Вы могли бы предоставить «myApplication» как имя мьютекса или «Глобальное\MyApplication», если вы хотите, чтобы ваш мьютекс был общим для всех сеансов Windows.
Изменить: это не так сложно, как кажется:), и я считаю это чистым.
Если вы используете Mutex, логически, что Mutex должен быть доступен из любой JVM, на которой была запущена копия «программы». В программировании на языке C это может быть выполнено через разделяемую память, но по умолчанию у Java этого нет.
При таком понимании существует множество способов реализовать то, что вы хотите. Вы можете открыть сокет сервера на назначенном порту (операционная система гарантирует, что только один процесс является получателем серверного сокета, а последующие открываются сбой).
Вы можете использовать «файл блокировки», но это немного сложно, так как файл, который вам нужно использовать, действительно будет каталогом (и он сильно зависит от того, является ли создание каталога атомарным для вашей файловой системы, хотя большинство созданий каталога). Если системный администратор решает запустить вас через NFS, все становится еще сложнее (если не невозможно).
Вы также можете сделать несколько отличных трюков с JVM и отладкой /JMI, если вы можете как-то убедиться, что все соответствующие JVM запускаются с одинаковыми конфигурациями (со временем, невыполнимой задачей).
Другие люди использовали средство exec для выполнения эквивалента списка процессов, но это немного сложно из-за возможности условия гонки (два процесса одновременно проверяют и не видят друг друга).
В конце концов, маршрут сокета сервера, вероятно, является наиболее стабильным, так как он гарантированно связывается только с одним процессом стеком TCP/IP (и опосредуется операционной системой). Тем не менее, вам нужно будет очистить сокет входящих сообщений, и это откроет возможность других проблем с безопасностью.
Вот один из методов, который использует автоматически заблокированный файл в домашнем каталоге пользователя. Имя основано на том, откуда бежит банка.
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_]", "_"); >>
Проверка метода блокировки 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(); >>