Предотвращение запуска нескольких экземпляров приложения Java
Я хочу, чтобы пользователь не запускал мое Java-приложение несколько раз параллельно.
Чтобы предотвратить это, я создал файл блокировки при открытии приложения и удаляю файл блокировки при закрытии приложения.
Когда приложение запущено, вы не можете открыть другой экземпляр jar. Однако, если вы убили приложение через диспетчер задач, событие закрытия окна в приложении не запускается, и файл блокировки не удаляется.
Как я могу убедиться, что метод файла блокировки работает или какой другой механизм я могу использовать?
Ответы
Ответ 1
Подобное обсуждение
http://www.daniweb.com/software-development/java/threads/83331
Привязать сервер. Если он не сможет привязать, то прекратите запуск. Поскольку ServerSocket может быть привязан только один раз, могут выполняться только одиночные инстанции программы.
И прежде чем вы спросите, нет. Просто потому, что вы связываете ServerSocket, не означает, что вы открыты для сетевого трафика. Это только вступает в силу после запуска программы "прослушивание" порта с помощью accept().
Ответ 2
Я вижу два варианта, которые вы можете попробовать:
- Используйте Java завершение работы
- Сделайте свой файл блокировки основным номером процесса. Процесс должен существовать, когда вы lanuch другого экземпляра. Если он не найден в вашей системе, вы можете предположить, что блокировка может быть отклонена и перезаписана.
Ответ 3
Вы можете написать идентификатор процесса процесса, создавшего файл блокировки в файле.
Когда вы сталкиваетесь с существующим файлом блокировки, вы не просто уходите, но вы проверяете, жив ли процесс с этим идентификатором. Если нет, вы можете продолжить.
Ответ 4
Вы можете использовать FileLock, это также работает в средах, где несколько пользователей используют порты:
String userHome = System.getProperty("user.home");
File file = new File(userHome, "my.lock");
try {
FileChannel fc = FileChannel.open(file.toPath(),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
FileLock lock = fc.tryLock();
if (lock == null) {
System.out.println("another instance is running");
}
} catch (IOException e) {
throw new Error(e);
}
Также выживает коллекция мусора.
Блокировка освобождается после завершения вашего процесса, не имеет значения, будет ли регулярный выход или сбой или что-то еще.
Ответ 5
Создание сокета сервера, привязанного к определенному порту с помощью экземпляра ServerSocket при запуске приложения, - прямой путь.
Обратите внимание, что блоки ServerSocket.accept()
, поэтому запускать его в собственном потоке имеет смысл не блокировать основной Thread
.
Вот пример с исключением, выданным при обнаружении:
public static void main(String[] args) {
assertNoOtherInstanceRunning();
... // application code then
}
public static void assertNoOtherInstanceRunning() {
new Thread(() -> {
try {
new ServerSocket(9000).accept();
} catch (IOException e) {
throw new RuntimeException("the application is probably already started", e);
}
}).start();
}
Ответ 6
Вы можете создать серверный сокет, например
new ServerSocket(65535, 1, InetAddress.getLocalHost());
в самом начале вашего кода. Затем, если исключение AddressAlreadyInUse попало в основной блок, вы можете отобразить соответствующее сообщение.
Ответ 7
.. какой другой механизм я мог бы использовать?
Если приложение. имеет графический интерфейс, который можно запустить с помощью Java Web Start. JNLP API, предоставляемый для веб-запуска, предлагает SingleInstanceService
. Вот моя демонстрационная версия . SingleInstanceService
.
Ответ 8
Вы можете написать что-то вроде этого.
Если файл существует, попробуйте удалить его. если он не может удалить. Можно сказать, что приложение уже запущено.
Теперь создайте тот же файл снова и перенаправьте sysout и syserr.
Это работает для меня
Ответ 9
Уже существуют доступные Java-методы в классе File для достижения того же. Метод is deleteOnExit(), который гарантирует, что файл будет автоматически удален при выходе из JVM. Тем не менее, он не обслуживает насильственные окончания. Нужно использовать FileLock в случае принудительного завершения.
Подробнее, проверьте https://docs.oracle.com/javase/7/docs/api/java/io/File.html
Таким образом, фрагмент кода, который может использоваться в основном методе, может выглядеть следующим образом:
public static void main(String args[]) throws Exception {
File f = new File("checkFile");
if (!f.exists()) {
f.createNewFile();
} else {
System.out.println("App already running" );
return;
}
f.deleteOnExit();
// whatever your app is supposed to do
System.out.println("Blah Blah")
}
Ответ 10
Некоторое время я боролся с этой же проблемой... ни одна из представленных здесь идей не сработала для меня. Во всех случаях блокировка (файл, сокет или другое) не сохранялась во втором экземпляре процесса, поэтому второй экземпляр все еще работал.
Поэтому я решил попробовать подход старой школы, чтобы просто создать файл .pid с идентификатором процесса первого процесса. Затем любой 2-й процесс завершит работу, если найдет файл .pid, а также подтвердится, что номер процесса, указанный в файле, все еще работает. Этот подход работал для меня.
Существует довольно много кода, который я предоставил здесь полностью для вашего использования... полное решение.
package common.environment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.nio.charset.Charset;
public class SingleAppInstance
{
private static final @Nonnull Logger log = LogManager.getLogger(SingleAppInstance.class.getName());
/**
* Enforces that only a single instance of the given component is running. This
* is resilient to crashes, unexpected reboots and other forceful termination
* scenarios.
*
* @param componentName = Name of this component, for disambiguation with other
* components that may run simultaneously with this one.
* @return = true if the program is the only instance and is allowed to run.
*/
public static boolean isOnlyInstanceOf(@Nonnull String componentName)
{
boolean result = false;
// Make sure the directory exists
String dirPath = getHomePath();
try
{
FileUtil.createDirectories(dirPath);
}
catch (IOException e)
{
throw new RuntimeException(String.format("Unable to create directory: [%s]", dirPath));
}
File pidFile = new File(dirPath, componentName + ".pid");
// Try to read a prior, existing pid from the pid file. Returns null if the file does not exist.
String oldPid = FileUtil.readFile(pidFile);
// See if such a process is running.
if (oldPid != null && ProcessChecker.isStillAllive(oldPid))
{
log.error(String.format("An instance of %s is already running", componentName));
}
// If that process isn't running, create a new lock file for the current process.
else
{
// Write current pid to the file.
long thisPid = ProcessHandle.current().pid();
FileUtil.createFile(pidFile.getAbsolutePath(), String.valueOf(thisPid));
// Try to be tidy. Note: This won't happen on exit if forcibly terminated, so we don't depend on it.
pidFile.deleteOnExit();
result = true;
}
return result;
}
public static @Nonnull String getHomePath()
{
// Returns a path like C:/Users/Person/
return System.getProperty("user.home") + "/";
}
}
class ProcessChecker
{
private static final @Nonnull Logger log = LogManager.getLogger(io.cpucoin.core.platform.ProcessChecker.class.getName());
static boolean isStillAllive(@Nonnull String pidStr)
{
String OS = System.getProperty("os.name").toLowerCase();
String command;
if (OS.contains("win"))
{
log.debug("Check alive Windows mode. Pid: [{}]", pidStr);
command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\"";
}
else if (OS.contains("nix") || OS.contains("nux"))
{
log.debug("Check alive Linux/Unix mode. Pid: [{}]", pidStr);
command = "ps -p " + pidStr;
}
else
{
log.warn("Unsupported OS: Check alive for Pid: [{}] return false", pidStr);
return false;
}
return isProcessIdRunning(pidStr, command); // call generic implementation
}
private static boolean isProcessIdRunning(@Nonnull String pid, @Nonnull String command)
{
log.debug("Command [{}]", command);
try
{
Runtime rt = Runtime.getRuntime();
Process pr = rt.exec(command);
InputStreamReader isReader = new InputStreamReader(pr.getInputStream());
BufferedReader bReader = new BufferedReader(isReader);
String strLine;
while ((strLine = bReader.readLine()) != null)
{
if (strLine.contains(" " + pid + " "))
{
return true;
}
}
return false;
}
catch (Exception ex)
{
log.warn("Got exception using system command [{}].", command, ex);
return true;
}
}
}
class FileUtil
{
static void createDirectories(@Nonnull String dirPath) throws IOException
{
File dir = new File(dirPath);
if (dir.mkdirs()) /* If false, directories already exist so nothing to do. */
{
if (!dir.exists())
{
throw new IOException(String.format("Failed to create directory (access permissions problem?): [%s]", dirPath));
}
}
}
static void createFile(@Nonnull String fullPathToFile, @Nonnull String contents)
{
try (PrintWriter writer = new PrintWriter(fullPathToFile, Charset.defaultCharset()))
{
writer.print(contents);
}
catch (IOException e)
{
throw new RuntimeException(String.format("Unable to create file at %s! %s", fullPathToFile, e.getMessage()), e);
}
}
static @Nullable String readFile(@Nonnull File file)
{
try
{
try (BufferedReader fileReader = new BufferedReader(new FileReader(file)))
{
StringBuilder result = new StringBuilder();
String line;
while ((line = fileReader.readLine()) != null)
{
result.append(line);
if (fileReader.ready())
result.append("\n");
}
return result.toString();
}
}
catch (IOException e)
{
return null;
}
}
}
Чтобы использовать его, просто вызовите его так:
if (!SingleAppInstance.isOnlyInstanceOf("my-component"))
{
// quit
}
Я надеюсь, что вы найдете это полезным.