Java установила обратный вызов из ExecutorService

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

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

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

Как правило, я бы добавил код к этому эффекту до конца метода run(). Однако некоторые другие объекты этого класса также вызывают эти потоки, но НЕ хотят, чтобы они записывали свои результаты в файл - вместо этого они используют свои результаты для выполнения других вычислений, которые в конечном итоге записываются в файл.

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

Возможно ли это?

Ответ 1

ExecutorService#submit return FutureTask<T>, который помогает вы получите результат, а метод ExecutorService#get заблокирует выполнение до тех пор, пока вычисление не будет завершено. Пример -

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Long> future = executor.submit(new Callable<Long>(){
       @Override
       public Long call() throws Exception {
           long sum = 0;
           for (long i = 0; i <= 10000000l; i++) {
               sum += i;
           }
           return sum;
       }
});
Long result = future.get();
System.out.println(result);

Ответ 2

Если вы используете Google Guava, вы можете использовать интерфейс ListenableFuture следующим образом:

  1. Преобразовать ExecutorService в ListeningExecutorService через MoreExecutors.listeningDecorator(existingExecutorService)
  2. Метод submit(Callable<V>) в ListeningExecutorService был сужен, чтобы вернуть ListenableFuture, который является подынтерфейсом Future.
  3. ListenableFuture имеет метод addListener(), так что вы можете зарегистрировать обратный вызов для запуска, когда будущее будет завершено.

Ответ 3

Итак, мне было интересно, можно ли подключить функцию обратного вызова к событию завершения потока с помощью ExecutorService.

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

Итак, вы сделали бы что-то вроде:

threadPool.submit(new ResultPrinter(myRunnable));
...

private static class ResultPrinter implements Runnable {
    private final MyRunnable myRunnable;
    public ResultPrinter(MyRunnable myRunnable) {
        this.myRunnable = myRunnable;
    }
    public void run() {
        myRunnable.run();
        Results results = myRunnable.getResults();
        // print results;
    }
}

Ответ 4

Вы можете добавить обратный вызов для случая, когда поток возвращается в Java 8+ с использованием CompletableFuture, как показано ниже, где t - результат ваших длительных вычислений,

CompletableFuture.supplyAsync(() -> {
    T t = new T();
    // do something
    return t;
}).thenApply(t -> {
    // process t
});

Если вы хотите использовать обратные вызовы только в Java 7, вы можете сделать что-то вроде

int x = 10;
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(x);
Future<T> result = fixedThreadPool.submit(() -> {
    // do calculation
    return T;
});
fixedThreadPool.submit(() -> {
    long minutesToWait = 5;
    T t = null;
    try {
        t = result.get(minutesToWait, TimeUnit.MINUTES);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        LOGGER.error(e);
    }
    if (t != null) {
        // process t
    }
});