прервать все потоки в Java в хуке выключения

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

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
        tmpDir.delete();
    }
}));

Моя проблема в том, что поток, который создает эти файлы, возможно, не завершился после запуска хука выключения, и, следовательно, может быть файл, созданный после вызова listFiles(). это приводит к тому, что каталог tmp не удаляется. Я придумал 2 хака вокруг этого:

Взлом №1:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        while (!tmp.delete()){
                for (File f : tmpDir.listFiles()) {
                f.delete();
            }
        }
    }
}));

Взлом №2:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
            tmpDir.delete();
        }
}));

Ни то, ни другое не является особенно хорошим решением. Было бы идеально, если бы хук выключения ждал, пока все потоки не будут завершены, прежде чем продолжить. Кто-нибудь знает, можно ли это сделать?


person ewok    schedule 19.07.2012    source источник
comment
Как насчет того, чтобы каждый поток очищал свои собственные временные файлы? Я считаю полезным иметь один и тот же класс, отвечающий за создание и удаление собственных временных файлов.   -  person ben_w    schedule 19.07.2012
comment
@erickson File.deleteOnExit(); работает с подфайлами, но не работает с каталогом tmp. предположительно причина в том, что нет гарантии, что все подфайлы будут удалены до того, как каталог попытается удалить. это приводит к непустому каталогу, который java не может удалить.   -  person ewok    schedule 19.07.2012
comment
@ben_w, как правило, это хорошая идея, но в структуре моего приложения есть фоновый поток, создающий файлы, которые будет использовать другой поток. поэтому, если они будут удалены после завершения фонового потока, приложение сломается.   -  person ewok    schedule 19.07.2012
comment
Совместное использование временных файлов между потоками и использование ShutdownHook для устранения пробелов в управлении ресурсами — очень плохая идея. Пересмотрите первоначальный дизайн.   -  person Alain O'Dea    schedule 09.06.2014


Ответы (3)


Просто отслеживайте все ваши запущенные потоки, а затем .join() их перед закрытием программы.

Это ответ на заголовок вопроса, так как эвок сказал, что не может использовать .deleteOnExit()

person Mitch Connor    schedule 19.07.2012
comment
Это очень плохая идея. Почти гарантировано, что прерывание не прерывается. - person Alain O'Dea; 05.06.2014
comment
Извини, я не понимаю, что ты имеешь в виду. Не могли бы вы уточнить или предложить свой собственный ответ о том, как это сделать правильно? - person Mitch Connor; 05.06.2014
comment
Краткий ответ: потоки — ужасная абстракция параллелизма при наличии глобального изменяемого состояния и должны умереть в огне. Если какой-либо из потоков находится в коде, который не проверяет прерывания или не обрабатывает их правильно (очень большая часть кода Java, который я видел в рабочей среде), то вызов .join() приведет к заблокировать навсегда. Лучше присоединиться с тайм-аутами и не удалять временные файлы. Есть лучшее решение, которое я разместил. - person Alain O'Dea; 07.06.2014
comment
Ваш ответ справедлив в рамках вопроса, поэтому я отозвал свой отрицательный голос. Я все еще думаю, что оригинальное решение ewok, совместное использование временных файлов между потоками и использование ShutdownHook, обречено на провал и нуждается в полной переработке. - person Alain O'Dea; 09.06.2014
comment
Я могу помочь людям только настолько, насколько они позволят мне. - person Mitch Connor; 09.06.2014

Что сказал Тайлер, но немного подробнее:

  • Сохраняйте ссылки на потоки, к которым может получить доступ перехватчик выключения.
  • Имейте прерывание вызова перехватчика выключения в потоках.
  • Просмотрите код потоков, чтобы убедиться, что они действительно реагируют на прерывание (вместо того, чтобы потреблять InterruptedException и ошибаться, что типично для большого количества кода). Прерывание должно побуждать поток прекратить зацикливание или блокировку, завершить незавершенные действия и завершиться.
  • Для каждого потока, в котором вы не хотите продолжать работу, пока он не завершится, проверьте, жив ли поток, и если это так, вызовите соединение с ним, установив тайм-аут на случай, если он не завершится в разумное время, и в этом случае вы можете решить удалять файл или нет.
person Nathan Hughes    schedule 20.07.2012
comment
Большая часть того, что вы сказали, будет достигнуто с помощью соединения, но да, безусловно, полезно. - person Mitch Connor; 20.07.2012

ОБНОВЛЕНИЕ: Тайлер Хейкс точно указал, что deleteOnExit() не является допустимым решением, так как ОП попробовал его, и оно не сработало. Я предлагаю альтернативное решение. Это снова косвенно, но в основном потому, что первоначальный дизайн, использующий потоки и ShutdownHook, имеет фатальные недостатки.

Используйте блоки finally для удаления временных файлов.

Полагаться на ShutdownHooks для управления ресурсами — очень плохая идея, из-за которой код очень сложно компоновать или повторно использовать в более крупной системе. Еще хуже идея передавать ресурсы из потока в поток. Ресурсы, такие как файлы и потоки, являются одними из самых опасных вещей для совместного использования между потоками. Вероятно, от этого мало что выиграет, и гораздо разумнее, чтобы каждый поток независимо получал временные файлы с помощью библиотечных методов createTempFile и управлял их использованием и удалением с помощью try/finally< /сильный>.

Соглашение о работе с временными файлами в системе состоит в том, чтобы рассматривать их как блок-боксы, где:

  1. расположение на диске непрозрачно (не имеет отношения к программе и не используется напрямую)
  2. имя файла не имеет значения
  3. имя файла гарантированно будет взаимоисключающим

Третьего выше очень трудно достичь, если вы вручную создаете и именуете временные файлы самостоятельно. Скорее всего, он будет хрупким и выйдет из строя в самый неподходящий момент (кто-нибудь пейджер в 3 часа ночи?).

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

Вот процесс высокого уровня:

  1. Получите Path с помощью Files.createTempFile() (или с помощью устаревшего кода до Java 7 File с File.createTempFile()). сильный>)
  2. Используйте временный файл по желанию
  3. Удалить файл

Это похоже на InputStream или другие ресурсы, которыми необходимо управлять вручную.

Этот общий шаблон для явного управления ресурсами (когда AutoCloseable и try-with-resources недоступны) выглядит следующим образом.

Resource r = allocateResource();
try {
   useResource(r);
} finally {
   releaseResource(r);
}

В случае Path это выглядит так:

Path tempDir = Paths.get("tmp/);
try {
    Path p = Files.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       Files.delete(f);
    }
} finally {
    Files.delete(tempDir);
}

В версиях до Java 7 использование с File выглядит следующим образом:

File tempDir = new File("tmp/");
try {
    File f = File.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       if (!f.delete()) {
          handleFailureToDeleteTempFile(f);
       }
    }
} finally {
    if (!tempDir.delete()) {
        handleFailureToDeleteTempDir(tempDir);
    }
}
person Alain O'Dea    schedule 07.06.2014
comment
OP заявил, что deleteOnExit() не вариант и не работает, поэтому мы предоставили альтернативный способ. Кроме того, я отвечал на вопрос в заголовке. - person Mitch Connor; 07.06.2014
comment
Туше. Я не читал комментарии. Это отличный пример последствий ручного управления подкаталогами temp. Может быть лучше просто использовать отдельные временные файлы и моделировать иерархии по мере необходимости, используя структуры данных в памяти. - person Alain O'Dea; 08.06.2014
comment
Не могли бы вы отменить свой голос против моего первоначального ответа? - person Mitch Connor; 08.06.2014