Tarefas Assíncronas com FutureTask, Callable e Future em Java
O método run() da interface Runnable, executado por uma thread, não retorna nenhum valor — apenas realiza uma ação. Entretanto, em muitos casos, o código executado em uma thread precisa produzir um resultado que possa ser recuperado depois. Para isso, o Java oferece os interfaces Callable e Future, definidos no pacote java.util.concurrent.
O Callable representa uma tarefa que retorna um resultado. Esse interface tem um parâmetro de tipo e um único método, call(), que devolve um valor desse tipo:
public interface Callable<V> {
V call() throws Exception;
}Em outras palavras, o método call() é executado em uma thread e retorna um resultado que pode ser obtido posteriormente.
A interface Future representa o resultado de uma tarefa assíncrona. Ele permite recuperar o valor produzido por um Callable assim que estiver disponível, além de oferecer meios para verificar o estado da execução e controlar sua finalização.
Alguns dos principais métodos de Future incluem:
boolean cancel(boolean mayInterruptIfRunning)– tenta cancelar a execução da tarefa.V get()– bloqueia até a tarefa terminar e retorna o resultado.V get(long timeout, TimeUnit unit)– aguarda o resultado por um tempo limitado.boolean isCancelled()– indica se a tarefa foi cancelada.boolean isDone()– indica se a tarefa terminou.Throwable exceptionNow()– retorna a exceção gerada pela tarefa, se houver.V resultNow()– retorna o resultado, se já estiver disponível, sem bloqueio.Future.State state()– retorna o estado da tarefa, que pode serCANCELLED,FAILED,RUNNINGouSUCCESS.
A Classe FutureTask
Uma das formas mais práticas de executar tarefas assíncronas é com a classe FutureTask, que implementa Future e Runnable. Ela representa uma tarefa que pode ser executada em uma thread e posteriormente cancelada, se necessário.
Por implementar Runnable, objetos de FutureTask podem ser passados diretamente a um construtor de Thread. Geralmente, FutureTask serve como uma "embalagem" para um Callable (ou, em alguns casos, um Runnable), permitindo executar a tarefa e obter o resultado no futuro.
Os principais construtores são:
FutureTask(Runnable runnable, V result)
FutureTask(Callable<V> callable)O primeiro cria uma tarefa a partir de um Runnable, devolvendo o valor especificado quando a execução termina. O segundo cria uma tarefa baseada em um Callable, retornando o resultado do método call().
Entre os métodos mais importantes de FutureTask estão get() (para obter o resultado), cancel() (para cancelar a tarefa) e isDone() (para verificar se a execução terminou).
Exemplo de Execução com FutureTask
import java.util.concurrent.*;
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
int number = 5; // número base para o cálculo do fatorial
Callable<Integer> task = () -> {
int n = number;
int result = 1;
while (n > 0) result *= n--;
return result;
};
var futureTask = new FutureTask<Integer>(task);
var t = new Thread(futureTask);
t.start();
System.out.println("Main thread works...");
try {
Integer factorial = futureTask.get();
System.out.printf("Factorial of %d is %d\n", number, factorial);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
System.out.println("Main thread finished...");
}
}Saída esperada:
Main thread started... Main thread works... Factorial of 5 is 120 Main thread finished...
O código define uma tarefa Callable que calcula o fatorial de um número e a executa em uma thread separada por meio de um FutureTask. O método get() bloqueia a thread principal até que o resultado esteja disponível, retornando o valor final.
Tratamento de Exceções
O método call() pode lançar exceções, e quando isso ocorre dentro de um FutureTask, a exceção é encapsulada em um ExecutionException. Assim, é possível capturar e tratar erros que acontecem durante a execução da tarefa.
import java.util.concurrent.*;
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
int number = -5; // número inválido para fatorial
try {
Callable<Integer> task = () -> {
if (number < 1) throw new Exception("Number must be greater than 0");
int n = number;
int result = 1;
while (n > 0) result *= n--;
return result;
};
var futureTask = new FutureTask<Integer>(task);
var t = new Thread(futureTask);
t.start();
System.out.println("Main thread works...");
Integer factorial = futureTask.get();
System.out.printf("Factorial of %d is %d\n", number, factorial);
}
catch (ExecutionException ex) {
System.out.println("Factorial error: " + ex.getMessage());
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
System.out.println("Main thread finished...");
}
}Saída:
Main thread started... Main thread works... Factorial error: java.lang.Exception: Number must be greater than 0 Main thread finished...
Cancelamento de Tarefas
A interface Future também permite cancelar tarefas em execução por meio do método cancel().
boolean cancel(boolean mayInterruptIfRunning)O parâmetro mayInterruptIfRunning indica se a thread que executa a tarefa pode ser interrompida. O método retorna true se o cancelamento for possível.
Exemplo:
import java.util.concurrent.*;
class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
int number = 5;
try {
Callable<Integer> task = () -> {
if (number < 1) throw new Exception("Number must be greater than 0");
int n = number;
int result = 1;
while (n > 0) {
result *= n--;
System.out.printf("[Factorial execution] result = %d n = %d\n", result, n);
Thread.sleep(200);
}
return result;
};
var futureTask = new FutureTask<Integer>(task);
var t = new Thread(futureTask);
t.start();
System.out.println("Main thread works...");
Thread.sleep(300);
futureTask.cancel(true);
if (futureTask.isCancelled())
System.out.println("Factorial execution cancelled...");
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
System.out.println("Main thread finished...");
}
}Saída esperada:
Main thread started... Main thread works... [Factorial execution] result = 5 n = 4 [Factorial execution] result = 20 n = 3 Factorial execution cancelled... Main thread finished...
Neste exemplo, a tarefa é interrompida após algumas iterações, demonstrando o cancelamento de uma execução assíncrona.
Resumo
Callabledefine uma tarefa que retorna um resultado.Futurerepresenta o resultado de uma tarefa assíncrona e permite controlá-la.FutureTaskcombina ambos, podendo ser executado em uma thread e cancelado.- O método
get()bloqueia até a conclusão e retorna o resultado. - O método
cancel()tenta interromper a execução de uma tarefa. - Exceções dentro de tarefas são encapsuladas em
ExecutionException.