A interface Java ExecutorService, java.util.concurrent.ExecutorService
, representa um mecanismo de execução assíncrona que é capaz de executar tarefas simultaneamente em segundo plano. Neste Java ExecutorService
tutorial vou explicar como criar um ExecutorService
, como submeter tarefas para execução, como ver os resultados dessas tarefas, e como desligar o ExecutorService
novamente quando for necessário.
Java ExecutorService Video Tutorial
Se preferir vídeo, tenho aqui uma introdução em vídeo:
br>
Task Delegation
Aqui está um diagrama que ilustra um tópico delegando uma tarefa a um Java ExecutorService
para execução assíncrona:
br>>p>p> Um fio delegando uma tarefa a um ExecutorService para execução assíncrona.>br>
Uma vez que o fio tenha delegado a tarefa ao ExecutorService
, o fio continua a sua própria execução independente da execução dessa tarefa. O ExecutorService
então executa a tarefa simultaneamente, independentemente do fio que submeteu a tarefa.
Java ExecutorService Example
Antes de nos aprofundarmos demasiado no ExecutorService
, vejamos um exemplo simples. Aqui está um simples Java ExecutorService
exemplo:
ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});executorService.shutdown();
Primeiro um ExecutorService
é criado usando o método Executors
newFixedThreadPool()
de fábrica. Isto cria um conjunto de fios com 10 fios executando tarefas.
Segundo, uma implementação anónima do método Runnable
interface é passada para o método execute()
. Isto faz com que o Runnable
seja executado por um dos fios do ExecutorService
.
Verá vários outros exemplos de como utilizar o ExecutorService
ao longo deste tutorial. Este exemplo serviu apenas para lhe dar uma rápida visão geral de como utilizar um ExecutorService
para executar tarefas em segundo plano parece-se com.
Java ExecutorService Implementations
O Java ExecutorService
é muito semelhante a um conjunto de fios. De facto, a implementação do pacote ExecutorService
interface presente no pacote java.util.concurrent
é uma implementação de “thread pool”. Se quiser compreender como a interface ExecutorService
pode ser implementada internamente, leia o tutorial acima.
Uma vez que ExecutorService
é uma interface, é necessário que as suas implementações sejam feitas de modo a fazer qualquer uso dela. O ExecutorService
tem a seguinte implementação no pacote java.util.concurrent
:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
Criar um ExecutorService
Como se cria um ExecutorService
depende da implementação que se utiliza. Contudo, pode usar o Executors
classe de fábrica para criar ExecutorService
instâncias também. Eis alguns exemplos de criação de um ExecutorService
:
ExecutorService executorService1 = Executors.newSingleThreadExecutor();ExecutorService executorService2 = Executors.newFixedThreadPool(10);ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
ExecutorService Usage
Há algumas formas diferentes de delegar tarefas para execução a um ExecutorService
:
- execute(Runnable)
- invokeAll(…)
li>submit(Runnable)li>submit(Callable)li>invokeAny(….)
Vou dar uma vista de olhos a cada um destes métodos nas secções seguintes.
Execute Runnable
O método Java ExecutorService
execute(Runnable)
toma um objecto java.lang.Runnable
, e executa-o de forma assíncrona. Eis um exemplo de execução de um Runnable
com um ExecutorService
:
ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});executorService.shutdown();
Não há forma de obter o resultado do objecto executado Runnable
, se necessário. Terá de utilizar um Callable
para isso (explicado nas secções seguintes).
Submit Runnable
O método Java ExecutorService
submit(Runnable)
também leva um Runnable
implementação, mas devolve um Future
objecto. Este Future
objecto pode ser utilizado para verificar se o Runnable
terminou a execução.
Aqui está um Java ExecutorService
submit()
exemplo:
Future future = executorService.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});future.get(); //returns null if the task has finished correctly.
O método submit()
devolve um objecto Java Futuro que pode ser usado para verificar quando o Runnable
estiver concluído.
Submit Callable
O método Java ExecutorService
submit(Callable)
é semelhante ao método submit(Runnable)
excepto que é necessário um método Java Callable em vez de um Runnable
. A diferença precisa entre um Callable
e um Runnable
é explicada um pouco mais tarde.
O resultado do Callable
pode ser obtido através do objecto Java Futuro devolvido pelo método submit(Callable)
. Aqui está um ExecutorService
Callable
exemplo:
Future future = executorService.submit(new Callable(){ public Object call() throws Exception { System.out.println("Asynchronous Callable"); return "Callable Result"; }});System.out.println("future.get() = " + future.get());
O exemplo de código acima irá produzir isto:
Asynchronous Callablefuture.get() = Callable Result
invokeAny()
O método invokeAny()
leva uma colecção de Callable
objectos, ou subinterfaces de Callable
. Invocar este método não devolve um Future
, mas devolve o resultado de um dos objectos de Callable
. Não tem qualquer garantia sobre qual dos resultados do Callable
obtém. Apenas um dos que terminam.
Se uma das tarefas Callable terminar, para que um resultado seja devolvido de invokeAny()
, então o resto das instâncias Callable são canceladas.
Se uma das tarefas terminar (ou lançar uma excepção), o resto das Callable
‘s são canceladas.
Aqui está um exemplo de código:
ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; }});String result = executorService.invokeAny(callables);System.out.println("result = " + result);executorService.shutdown();
Este exemplo de código imprimirá o objecto devolvido por um dos Callable
‘s na colecção dada. Já tentei executá-lo algumas vezes, e o resultado muda. Por vezes é “Tarefa 1”, outras vezes “Tarefa 2” etc.
invokeAll()
O método invokeAll()
invoca todos os Callable
objectos que lhe são passados na colecção passada como parâmetro. O invokeAll()
devolve uma lista de Future
objectos através dos quais se podem obter os resultados das execuções de cada Callable
.
Tenha em mente que uma tarefa pode terminar devido a uma excepção, pelo que pode não ter “tido sucesso”. Não há como num Future
dizer a diferença.
Aqui está um exemplo de código:
ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; }});List<Future<String>> futures = executorService.invokeAll(callables);for(Future<String> future : futures){ System.out.println("future.get = " + future.get());}executorService.shutdown();
Runnable vs. Chamável
A interface Runnable
é muito semelhante à interface Callable
. A interface Runnable representa uma tarefa que pode ser executada simultaneamente por uma thread ou um ExecutorService
. A Callable só pode ser executada por um ExecutorService. Ambas as interfaces têm apenas um único método. Há uma pequena diferença entre a interface Callable
e Runnable
. A diferença entre a interface Runnable
e Callable
é mais facilmente visível quando se vêem as declarações de interface.
Aqui está primeiro a Runnable
declaração de interface:
public interface Runnable { public void run();}
E aqui está a Callable
declaração de interface:
public interface Callable{ public Object call() throws Exception;}
A principal diferença entre o método Runnable
run()
e o método Callable
call()
método é que o call()
método pode devolver um Object
da chamada ao método. Outra diferença entre call()
e run()
é que call()
pode lançar uma excepção, enquanto run()
não pode (excepto excepções não verificadas – subclasses de RuntimeException
).
Se precisar de submeter uma tarefa a uma interface Java ExecutorService
e precisar de um resultado da tarefa, então precisa de fazer com que a sua tarefa implemente a interface Callable
. Caso contrário, a sua tarefa pode apenas implementar a interface Runnable
.
Cancelar tarefa
Pode cancelar uma tarefa (Runnable
ou Callable
) submetida a uma interface Java ExecutorService
chamando o método cancel()
no método Future
devolvido quando a tarefa é submetida. O cancelamento da tarefa só é possível se a tarefa ainda não tiver começado a ser executada. Eis um exemplo de cancelamento de uma tarefa chamando o Future.cancel()
method:
future.cancel();
ExecutorService Shutdown
Quando terminar de utilizar o Java ExecutorService
deverá desligá-lo, para que os fios não continuem a correr. Se a sua aplicação for iniciada através de um método main()
e o seu fio principal sair da sua aplicação, a aplicação continuará a correr se tiver um ExexutorService
activo na sua aplicação. Os fios activos dentro deste ExecutorService
impedem o encerramento da JVM.
shutdown()
para terminar os fios dentro do ExecutorService
chama o seu método shutdown()
. O ExecutorService
não se desligará imediatamente, mas deixará de aceitar novas tarefas, e assim que todos os fios tiverem terminado as tarefas actuais, o ExecutorService
desliga-se. Todas as tarefas submetidas ao ExecutorService
antes de shutdown()
ser chamado, são executadas. Eis um exemplo de execução de um Java ExecutorService
shutdown:
executorService.shutdown();
shutdownNow()
Se quiser encerrar o método ExecutorService
imediatamente, pode chamar o método shutdownNow()
. Isto tentará parar imediatamente a execução de todas as tarefas, e salta todas as tarefas submetidas, mas não processadas. Não há garantias dadas sobre as tarefas de execução. Talvez elas parem, talvez a execução até ao fim. É uma tentativa de melhor esforço. Eis um exemplo de chamada ExecutorService
shutdownNow
:
executorService.shutdownNow();
awaitTermination()
O método ExecutorService
awaitTermination()
irá bloquear o fio que o chama até que o método ExecutorService
se desligue completamente, ou até que ocorra um determinado período de tempo. O método awaitTermination()
é tipicamente chamado após chamar shutdown()
ou shutdownNow()
. Aqui está um exemplo de chamada ExecutorService
awaitTermination()
:
executorService.shutdown();executorService.awaitTermination(10_000L, TimeUnit.MILLISECONDS );