На FutureTask, наконец и TimeoutExceptions в Java - PullRequest
4 голосов
/ 16 сентября 2009

Я пытаюсь понять, как обеспечить выполнение определенного действия за определенное время. Похоже на простую работу для новой библиотеки Java util.concurrent. Тем не менее, эта задача требует подключения к базе данных, и я хочу быть уверенным, что она правильно освобождает это соединение по истечении времени ожидания.

так называть услугу:

int resultCount = -1;
ExecutorService executor = null;
try {
 executor = Executors.newSingleThreadExecutor();
 FutureTask<Integer> task = new CopyTask<Integer>();
 executor.execute(task);
 try {
  resultCount = task.get(2, TimeUnit.MINUTES);
 } catch (Exception e) {
   LOGGER.fatal("Migrate Events job crashed.", e);
   task.cancel(true);
   return;
 }
} finally {
if (executor != null) {
 executor.shutdown();
}

Сама задача просто оборачивает вызываемый объект, вот метод вызова:

@Override
public Integer call() throws Exception {
 Session session = null;
 try {
  session = getSession();
  ... execute sql against sesssion ...
  }
 } finally {
  if (session != null) {
   session.release();
  }
 }
}

Итак, мой вопрос к тем, кто сделал это так далеко: гарантированно ли вызывается session.release () в случае сбоя задачи из-за TimeoutException? Я утверждаю, что это не так, но я бы хотел оказаться неправым.

Спасибо

edit : У меня проблема в том, что иногда рассматриваемый sql не заканчивается из-за странных проблем с БД. Итак, я хочу просто закрыть соединение, позволить db откатить транзакцию, немного отдохнуть и повторить попытку позже. Поэтому я отношусь к get (...) так, как будто это убивает thead. Это неправильно?

Ответы [ 4 ]

11 голосов
/ 25 сентября 2009

Когда вы вызываете task.get() с тайм-аутом, этот тайм-аут применяется только к попытке получить результаты (в вашем текущем потоке), а не к самому вычислению (в рабочем потоке). Отсюда ваша проблема здесь; если рабочий поток попадает в какое-то состояние, из которого он никогда не вернется, то тайм-аут просто гарантирует, что ваш код опроса будет продолжать работать, но не будет влиять на работника.

Ваш звонок на task.cancel(true) в блоке catch - это то, что я изначально собирался предложить, и это хорошая практика кодирования. К сожалению, это только устанавливает флаг в потоке, который может / должен быть проверен хорошо ведущимися долго выполняемыми отменяемыми задачами, но он не предпринимает никаких прямых действий в другом потоке. Если методы выполнения SQL не объявляют, что они выдают InterruptedException, то они не собираются проверять этот флаг и не будут прерываться через типичный механизм Java.

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

В этом конкретном случае, я ожидаю, что, вероятно, есть некоторые параметры, которые вы могли бы установить для соединения с БД, чтобы вызовы SQL истекали по истечении заданного периода, что означает, что элемент управления возвращается в ваш код Java (возможно, с некоторым исключением) поэтому вызывается блок finally. Если нет, т. Е. Нет способа заставить вызов базы данных (например, PreparedStatement.execute()) вернуть управление через некоторое предварительно определенное время, то вам нужно будет создать дополнительный поток в вашем Callable, который может отслеживать тайм-аут и принудительно закрывать соединение / сеанс, если он истекает. Это не очень хорошо, и ваш код станет намного чище, если вы сможете заставить SQL-вызовы взаимодействовать.

(По иронии судьбы, несмотря на то, что вы предоставили достаточное количество кода для поддержки этого вопроса, действительно важной частью является бит, который вы отредактировали: "... execute sql against sesssion ...": -))

2 голосов
/ 16 сентября 2009

Блок finally в конечном итоге будет выполнен.

Когда ваша задача занимает больше 2 минут, выдается TimeoutException, но фактический поток продолжает выполнять свою работу и в конце концов вызывает блок finally Даже если вы отмените задачу и вызовете прерывание, будет вызван блок finally.

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

public static void main(String[] args) {
    int resultCount = -1;
    ExecutorService executor = null;
    try {
        executor = Executors.newSingleThreadExecutor();
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                try {
                    Thread.sleep(10000);
                    return 1;
                } finally {
                    System.out.println("FINALLY CALLED!!!");
                }
            }
        });
        executor.execute(task);
        try {
            resultCount = task.get(1000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            System.out.println("Migrate Events job crashed: " + e.getMessage());
            task.cancel(true);
            return;
        }
    } finally {
        if (executor != null) {
            executor.shutdown();
        }
    }
}
2 голосов
/ 16 сентября 2009

Вы не можете прерывать поток извне, поэтому тайм-аут не будет влиять на код внизу уровня JDBC (возможно, даже где-то в JNI-пространстве). Предположительно, в конце концов работа SQL закончится и session.release () произойдет, но это может занять много времени после окончания вашего тайм-аута.

0 голосов
/ 16 сентября 2009

Ваш пример говорит:

copyRecords.cancel(true);

Я предполагаю, что это должно было сказать:

task.cancel(true);

Ваш блок finally будет вызываться при условии, что содержимое блока try является прерываемым. Некоторые операции (например, wait ()), некоторые операции - нет (например, InputStream # read ()). Все зависит от операции, которую блокирует код при прерывании задачи.

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