Почему File.exists() работает нестабильно в многопоточной среде?

У меня есть пакетный процесс, работающий под java JDK 1.7. Он работает в системе с RHEL, 2.6.18-308.el5 #1 SMP.

Этот процесс получает список объектов метаданных из базы данных. Из этих метаданных он извлекает путь к файлу. Этот файл может существовать, а может и не существовать.

Процесс использует ExecutorService (Executors.newFixedThreadPool()) для запуска нескольких потоков. Каждый поток запускает Callable, который запускает процесс, который читает этот файл и записывает другой файл, если этот входной файл существует (и регистрирует результат), и ничего не делает, если файл не существует (за исключением регистрации этого результата).

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

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

ОБНОВЛЕНИЕ: вот фактический код процесса unix, вызываемого рабочими потоками в этом сценарии:

public int convertOutputFile(String inputFile, String outputFile)
throws IOException
{
    List<String> args = new LinkedList<String>();
    args.add("sox");
    args.add(inputFile);
    args.add(outputFile);
    args.addAll(2, this.outputArguments);
    args.addAll(1, this.inputArguments);
    long pStart = System.currentTimeMillis();
    int status = -1;
    Process soxProcess = new ProcessBuilder(args).start();

    try {
        // if we don't wait for the process to complete, player won't
        // find the converted file.
        status = soxProcess.waitFor();
        if (status == 0) {
            logger.debug(String.format("SoX conversion process took %d ms.",
                    System.currentTimeMillis() - pStart));
        } else {
            logger.error("SoX conversion process returned an error status of " + status);
        }
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return status;
}

ОБНОВЛЕНИЕ №2:

Я попробовал эксперимент по переключению с java.io.File.exists() на java.nio.Files.exists(), и это, кажется, обеспечивает большую надежность. Мне еще предстоит увидеть состояние отказа при нескольких попытках, тогда как раньше это происходило примерно в 10% случаев. Итак, я думаю, я хочу узнать, является ли версия nio более надежной в том, как она обрабатывает базовую файловую систему. Этот вывод позже оказался ложным. nio здесь не поможет.

ОБНОВЛЕНИЕ №3: при дальнейшем рассмотрении я все еще обнаруживаю, что происходит то же самое сбойное состояние. Так что переход на nio не панацея. Я получил лучшие результаты, уменьшив размер пула потоков службы-исполнителя до 1. Это кажется более надежным, и таким образом нет шансов, что один поток будет читать каталог, в то время как другой поток запускает процесс, который записывает в тот же каталог.

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

ОБНОВЛЕНИЕ №4: перекодирование таким образом, чтобы выходные файлы записывались в другой каталог, чем входные файлы (существование которых проверяется), особо не помогает. Единственное изменение, которое помогает, — размер пула потоков ExecutorService, равный 1, другими словами, отсутствие многопоточности в этой операции.


person Steve Cohen    schedule 06.01.2016    source источник
comment
Ваш вопрос очень неясен. Пожалуйста, не могли бы вы опубликовать MCVE?   -  person Andy Turner    schedule 07.01.2016
comment
Я не могу легко опубликовать MCVE. Извини. Тем не менее, я указал, что каждый вызов рабочего потока запускает процесс (процесс unix), и этот процесс читает файл и записывает другой (в тот же каталог). Будет ли File.exists() давать плохие результаты в многопоточной среде. в котором содержимое каталога меняется, и может ли nio.Files.exists() дать более надежные результаты?   -  person Steve Cohen    schedule 07.01.2016
comment
Вопросы, требующие помощи в отладке (почему этот код не работает?), должны включать желаемое поведение, конкретную проблему или ошибку и кратчайший код, необходимый для их воспроизведения, в самом вопросе. Вопросы без четкой формулировки проблемы бесполезны для других читателей.   -  person Andy Turner    schedule 07.01.2016
comment
@SteveCohen Нет. Скорее всего, в вашем коде ошибка. Но трудно сказать, что, мы ничего не знаем о том, какая из ваших задач что делает. в каком порядке и в каком потоке, или есть ли какие-либо условия гонки и т. д.   -  person nos    schedule 07.01.2016
comment
Кроме того, я сомневаюсь, что ваша программа порождает новые процессы unix. Скорее всего, он просто порождает собственные потоки, что полностью отличается от другого целого процесса с JVM. Если бы вы были ps --forest aux | grep java, не было бы 30 процессов jvm, только один.   -  person MeetTitan    schedule 07.01.2016
comment
@MeetTitan - моя программа совершенно определенно и явно порождает новые процессы unix. Я обновлю исходный вопрос, показав, как именно они запускаются. Все эти вызовы выполняются в рабочих потоках после того, как мы определяем, что входной файл существует.   -  person Steve Cohen    schedule 07.01.2016
comment
И я никогда не говорил, что существует несколько процессов JVM.   -  person Steve Cohen    schedule 07.01.2016
comment
@SteveCohen, если вы не используете Runtime.exec, вы определенно не создаете процессы, а вместо этого создаете потоки. Многопроцессорность != многопоточность.   -  person MeetTitan    schedule 07.01.2016
comment
Хм, @MeetTitan, javadoc для java.lang.Process говорит: методы ProcessBuilder.start() и Runtime.exec создают собственный процесс и возвращают экземпляр подкласса Process, который можно использовать для управления процессом и получения информации о Это. Класс Process предоставляет методы для выполнения ввода из процесса, выполнения вывода в процесс, ожидания завершения процесса, проверки состояния выхода процесса и уничтожения (уничтожения) процесса.   -  person Steve Cohen    schedule 07.01.2016
comment
О, вы используете ProcessBuilder? Продолжайте, гражданин.   -  person MeetTitan    schedule 08.01.2016


Ответы (3)


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

person Olivier    schedule 06.01.2016
comment
В то время как отрицательные голоса за рулем действительно отстой, ваш ответ больше похож на комментарий. - person MeetTitan; 07.01.2016
comment
@Olivier - я проголосовал за вас, так как вы, по крайней мере, получили ответ на вопрос, на который я пытаюсь ответить, даже если не авторитетно, что, опять же, насколько надежен File.exists() на машине, на которой одновременно происходит много операций чтения и записи. . Я не верю, что здесь проблема с файловыми дескрипторами. но, забыв на мгновение о многопоточности в java, представьте себе сервер, на котором несколько НЕЗАВИСИМЫХ процессов выполняют запись на диск. Если бы эти записи были иногда в один и тот же каталог, могло бы приложение java, вызывающее File.exists(), иногда давать сбой? - person Steve Cohen; 07.01.2016
comment
Спасибо за голосование, я предпочитаю пытаться ответить, а не переходить на мета и кричать MCVE!;) File.exist() по своей сути подвержен ошибкам, поскольку, как я объясняю в своем ответе, он возвращает true, если все идет хорошо И каталог существует , или false в любых других обстоятельствах, что в основном означает, что я не могу подтвердить, что он существует, а не его не существует. Кстати, это применимо к большинству методов File. - person Olivier; 07.01.2016
comment
Моя философия похожа на вашу. Есть проблемы, которые нельзя легко и кратко сформулировать, и обсуждение, которое они провоцируют, может быть полезным, даже если оно не приводит к быстрому и четкому решению. Не могли бы вы прокомментировать мое обновление, в котором сообщается, что java.nio.Files.exists() кажется более надежным, чем java.io.File.exists()? - person Steve Cohen; 07.01.2016

Я отметил ответ @Olivier как «ответ», но я предоставляю здесь свой собственный, чтобы обобщить результаты моего эксперимента. Я называю это «ответом» за то, что он ближе всех к истине, хотя его догадка о дескрипторах файлов не кажется очевидно правильной, хотя я также не могу ее опровергнуть. Что звучит правдоподобно, так это его простое утверждение: «Ваше приложение может быть должным образом многопоточным, когда бы вы ни обращались к файловой системе, у него есть ограничения». Это согласуется с моими выводами. Если кто-то может пролить дополнительный свет, я могу изменить это.

  1. Это ошибка в моем коде?

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

  1. Помогает ли использование java.nio.Files.exists() вместо java.io.File.exists()?

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

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

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

  1. Помогает ли уменьшение количества потоков в пуле?

Только уменьшение его до 1 делает его надежным, другими словами, помогает только полный отказ от многопоточного подхода. Эта операция не кажется на 100% надежной, по крайней мере, не с этой ОС и многопоточным JDK.

Если бы sox когда-либо был переработан, чтобы давать отдельный код ошибки для файла, не найденного во входных файлах, это могло бы сделать ответ @EJP выше возможным.

person Steve Cohen    schedule 07.01.2016

Настоящий вопрос здесь заключается в том, почему вы звоните ему?

  • Вы должны построить FileInputStream или FileReader, чтобы прочитать файл, и они выдадут FileNotFoundException, если файл не может быть открыт, с абсолютной надежностью.
  • Вы все равно должны ловить исключения.
  • Операционная система все равно должна проверить, существует ли файл.
  • Нет необходимости проверять его дважды.
  • Существование может измениться между проверкой и открытием файла.

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

Является ли ошибкой запись файлов в многопоточном процессе?

Я бы не сказал, что это ошибка, но это довольно бессмысленно. Диск не многопоточный.

Поможет ли меньший пул потоков (в настоящее время 30)?

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

person user207421    schedule 06.01.2016
comment
Ну нет. Исключение не будет выброшено, но порожденный процесс завершится сбоем, и этот процесс (который запускает исполняемый файл SoX) не выдает значимых кодов ошибок, которые позволили бы мне определить, был ли причиной сбоя FileNotFound, что оказалось важной информацией в Это дело. Я могу попробовать ваше предложение уменьшить количество потоков. - person Steve Cohen; 07.01.2016
comment
Хотя я уже повысил надежность, переключившись с java.io.File.exists() на java.nio.Files.exists(), я сообщу и поддержу ваш ответ, потому что ваше предложение уменьшить количество потоков действительно улучшило пропускную способность. . Спасибо. - person Steve Cohen; 07.01.2016
comment
Сколько потоков выполнялось одновременно? О Files.exists() см. docs.oracle.com /javase/tutorial/essential/io/check.html : это не намного безопаснее, чем File.exists(), поэтому наблюдаемое улучшение может вообще не быть связано. - person Olivier; 07.01.2016
comment
30. Я уменьшил его до 6 в соответствии с @EJB, и это действительно улучшило пропускную способность. - person Steve Cohen; 07.01.2016