Невозможно заблокировать файлы в Linux с помощью java.nio.channels.FileLock

Я создавал приложение на Java, для которого мне нужен только один экземпляр. Для этого я создал файл и получил блокировку во время работы моего приложения.

У меня есть следующий код, который работает в Windows, но не работает в Linux: как только я получу блокировку, не разблокируя ее, я могу получить другую блокировку.

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

public class MyApp {

private static File f;
private static FileChannel channel;
private static FileLock lock;

public static void main(String[] args) {
    try {
        f = new File("RingOnRequest.lock");
        // Check if the lock exist
        if (f.exists()) {
            // if exist try to delete it
            f.delete();
        }
        // Try to get the lock
        channel = new RandomAccessFile(f, "rw").getChannel();
        lock = channel.tryLock();
        if(lock == null)
        {
            // File is lock by other application
            channel.close();
            throw new RuntimeException("Only 1 instance of MyApp can run.");
        }
        // Add shutdown hook to release lock when application shutdown
        ShutdownHook shutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        //Your application tasks here..
        System.out.println("Running");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    catch(IOException e)
    {
        throw new RuntimeException("Could not start process.", e);
    }

}

public static void unlockFile() {
    // release and delete file lock
    try {
        if(lock != null) {
            lock.release();
            channel.close();
            f.delete();
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
}

static class ShutdownHook extends Thread {

    public void run() {
        unlockFile();
    }
}

}

person kaysush    schedule 26.06.2012    source источник
comment
как только я получу замок, не открывая его, я могу получить другой замок на нем.   -  person kaysush    schedule 26.06.2012
comment
Это потому, что вы удаляете файл. Не удаляйте файл. Вы не можете рассчитывать на монопольную блокировку файла при создании нового файла для каждого экземпляра программы.   -  person user207421    schedule 09.05.2014


Ответы (6)


Я использовал тот же образец, что и вы, и получил ту же проблему в Mac OS X. Похоже, что блокировка файла не предотвращает удаление файла в системах POSIX. Ваше приложение по-прежнему будет иметь какой-то дескриптор этого файла, пока вы его не разблокируете. Поэтому рассмотрите возможность использования файла блокировки с PID в его имени (или внутри файла).

person Aleksandr Kravets    schedule 26.06.2012
comment
Как изменится добавление PID к имени файла? - person aggregate1166877; 02.10.2012
comment
Это еще один способ реализации файлов блокировки. Ваша программа создает файл с PID в его имени (myprogram.PID) или внутри него. Следующий запущенный экземпляр проверит файлы, соответствующие используемому шаблону, извлечет PID и проверит, есть ли процесс с этим PID. Если процесс существует - текущий экземпляр является вторым и должен быть закрыт, в противном случае - это единственный экземпляр, он создает новый файл PID и продолжает работу. - person Aleksandr Kravets; 02.10.2012
comment
-1: PID-файлы не обеспечивают реальной блокировки, поскольку их создание, модификация, чтение и т. д. не является атомарным и поэтому подвержен гонкам. Таким образом, их необходимо использовать в сочетании с другим механизмом блокировки, чтобы сделать их безопасными. - person Charles Duffy; 18.12.2013

Вы удаляете файл блокировки при каждом запуске, поэтому только один процесс может заблокировать его.

Когда вы используете FileLock, чисто рекомендательно получение блокировки файла не может помешать вам что-либо делать: чтение, запись и удаление файла могут быть возможны, даже если другой процесс установил блокировку. Иногда блокировка может делать больше, чем это на конкретной платформе, но это поведение не указано, и полагаться на больше, чем гарантируется в документации по классу, — прямой путь к провалу.

«Рекомендательная блокировка» — это лишь сигнал, который виден другим процессам, которые пытаются его найти. Если вы зависите от него больше, чем это, ваша программа сломается при запуске на какой-либо другой платформе.

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

person erickson    schedule 26.06.2012

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

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

ИЗМЕНИТЬ:

Кроме того, из файла javadoc FileLock:

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

person WhyNotHugo    schedule 26.06.2012
comment
Как я могу это сделать программно? - person kaysush; 26.06.2012
comment
Я нашел решение здесь advancedinstaller.com/user-guide/ - person kaysush; 26.06.2012
comment
На самом деле это верно, если вы запускаете этот предварительный установщик, а не в обычном приложении Java. - в Интернете нет другой ссылки на этот secondMain, и я проверил его, и main() вызывается каждый раз. - person WhyNotHugo; 26.06.2012
comment
Получил блокировку, создав этот файл в каталоге java.io.tmpdir. - person kaysush; 26.06.2012
comment
-1: создание и изменение pid-файлов не является атомарным, как и процесс проверки того, на что они указывают (обязательно выполняется асинхронно после чтения). Это делает их небезопасными для реальной блокировки. - person Charles Duffy; 18.12.2013
comment
pidfiles - это то, что повсеместно используется для такого рода сценариев (посмотрите, например, на демоны Unix). Создание файла является атомарным. Если вы читаете пустой pid-файл, просто поспите 500 мс и перечитайте. Если он все еще пуст, значит, он остался от мертвого экземпляра. - person WhyNotHugo; 27.12.2013

Используйте 1_. В системах Unix это атомарная операция – он будет успешным, если новый каталог будет успешно создан, в противном случае произойдет сбой.

Пример:

File lockFile = new File("/path/to/lockdir");
boolean hasLock = lockFile.mkdir();
if (!hasLock) {
  throw new IOException("could not get lock");
}
// do stuff
lockFile.delete();
person Nik    schedule 14.08.2015
comment
Что делать, если приложение по какой-либо причине неожиданно прекратило работу без удаления папки lockFile? .. В следующий раз, когда вы запустите приложение, оно не запустится, потому что папка существует! - person Brad; 15.08.2020

Я тестировал его как на Windows, так и на Linux. Работает отлично. Файл блокировки автоматически удаляется при нормальном закрытии приложения. Поэтому вам не нужно беспокоиться о том, что файл блокировки останется там, когда вы перезапустите приложение. Просто закомментируйте следующие строки:

if (f.exists()) {
    // if exist try to delete it
    f.delete();
}

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

person Jimmy Q    schedule 11.01.2017

Недавно я столкнулся с такой же проблемой, но в моем случае у меня было преимущество: мое приложение опрашивало какой-то каталог только после некоторого тайм-аута. Поскольку мое приложение не сразу опрашивало каталог, я написал специальный класс, который создает файл блокировки со своим собственным PID внутри в методе инициализации, после чего, прежде чем он попытается работать с каталогом, ему нужно вызвать ownedLock() - если он возвращает true, тогда мы можем работать иначе выход (код в Котлине, но вы поймете основную идею):

import java.io.File
import java.lang.management.ManagementFactory
class DirectoryLocker(private val directory: String, private val lockName: String) {
   private val lockFile by lazy { File("$directory/$lockName.lock") }

   // Will try to acquire lock to directory, whoever last writes its pid to file owns the directory
   fun acquireLock() = with(lockFile) {
      createNewFile()
      writeText(currentPID())
   }

   fun ownedLock(): Boolean = lockFilePid() == currentPID()

   fun releaseOwnedLock() {
      if(lockFilePid() == currentPID()) lockFile.delete()
   }

   private fun currentPID(): String {
      val processName = ManagementFactory.getRuntimeMXBean().name
      return processName.split("@".toRegex()).first()
   }

   private fun lockFilePid(): String? {
      return if(lockFile.exists()) lockFile.readLines().first() else null
   }
}
person w00lf    schedule 08.12.2017