Executável vs. Chamadas em Java

Executável vs. Chamadas em Java

1. Visão geral

Desde os primeiros dias do Java, o multithreading tem sido um aspecto importante da linguagem. Runnable é a interface principal fornecida para representar tarefas multithread eCallable é uma versão aprimorada deRunnable que foi adicionada no Java 1.5.

Neste artigo, exploraremos as diferenças e as aplicações de ambas as interfaces.

2. Mecanismo de Execução

Ambas as interfaces são projetadas para representar uma tarefa que pode ser executada por vários encadeamentos. Runnable tarefas podem ser executadas usando a classeThread ouExecutorService, enquantoCallables podem ser executadas apenas usando a última.

3. Retornar valores

Vamos dar uma olhada mais profunda na forma como essas interfaces lidam com os valores de retorno.

3.1. ComRunnable

A interfaceRunnable é uma interface funcional e tem um único métodorun() que não aceita nenhum parâmetro e não retorna nenhum valor.

Isso é adequado para situações em que não estamos procurando um resultado da execução do encadeamento, por exemplo, log de eventos recebidos:

public interface Runnable {
    public void run();
}

Vamos entender isso com um exemplo:

public class EventLoggingTask implements  Runnable{
    private Logger logger
      = LoggerFactory.getLogger(EventLoggingTask.class);

    @Override
    public void run() {
        logger.info("Message");
    }
}

Neste exemplo, o encadeamento apenas lê uma mensagem da fila e a registra em um arquivo de log. Nenhum valor retornou da tarefa; a tarefa pode ser iniciada usandoExecutorService:

public void executeTask() {
    executorService = Executors.newSingleThreadExecutor();
    Future future = executorService.submit(new EventLoggingTask());
    executorService.shutdown();
}

Nesse caso, o objetoFuture não terá nenhum valor.

3.2. ComCallable

A interfaceCallable é uma interface genérica contendo um único métodocall() - que retorna um valor genéricoV:

public interface Callable {
    V call() throws Exception;
}

Vamos dar uma olhada no cálculo do fatorial de um número:

public class FactorialTask implements Callable {
    int number;

    // standard constructors

    public Integer call() throws InvalidParamaterException {
        int fact = 1;
        // ...
        for(int count = number; count > 1; count--) {
            fact = fact * count;
        }

        return fact;
    }
}

O resultado do métodocall() é retornado em um objetoFuture:

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future future = executorService.submit(task);

    assertEquals(120, future.get().intValue());
}

4. Manipulação de exceção

Vamos ver como eles são adequados para tratamento de exceções.

4.1. ComRunnable

Uma vez que a assinatura do método não tem a cláusula “throws” especificada, __ não há como propagar mais exceções verificadas.

4.2. ComCallable

O métodoCallable’s call() contém a cláusula “throwsException” para que possamos propagar mais facilmente as exceções verificadas:

public class FactorialTask implements Callable {
    // ...
    public Integer call() throws InvalidParamaterException {

        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
    // ...
    }
}

No caso de execução deCallable using anExecutorService,, as exceções são coletadas no objetoFuture, que podem ser verificadas fazendo uma chamada ao métodoFuture.get(). Isso lançará umExecutionException – que envolve a exceção original:

@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {

    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future future = executorService.submit(task);
    Integer result = future.get().intValue();
}

No teste acima, oExecutionException está sendo lançado porque estamos passando um número inválido. Podemos chamar o métodogetCause() neste objeto de exceção para obter a exceção original verificada.

Se não fizermos a chamada para o métodoget() da classeFuture - então a exceção lançada pelo métodocall() não será relatada de volta, e a tarefa ainda será marcada como concluída :

@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future future = executorService.submit(task);

    assertEquals(false, future.isDone());
}

O teste acima será aprovado com sucesso, embora tenhamos lançado uma exceção para os valores negativos do parâmetro paraFactorialCallableTask.

5. Conclusão

Neste artigo, exploramos as diferenças entre as interfacesRunnableeCallable.

Como sempre, o código completo deste artigo está disponívelover on GitHub.