Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprenda Executores e Pool de Threads | Mecanismos de Sincronização de Alto Nível
Quizzes & Challenges
Quizzes
Challenges
/
Multithreading em Java

bookExecutores e Pool de Threads

Já exploramos uma variedade de mecanismos para suportar multithreading, e Executors é um deles!

O que são Executors e Thread Pooling?

Executors é um mecanismo que oferece abstrações de alto nível para o gerenciamento de threads. Permite criar e gerenciar um pool de threads, que consiste em um conjunto de threads pré-existentes prontas para executar tarefas. Em vez de criar uma nova thread para cada tarefa, as tarefas são enviadas para o pool, onde sua execução é distribuída entre as threads.

Então, o que exatamente é um thread pool? Trata-se de uma coleção de threads pré-existentes prontas para executar tarefas. Ao utilizar um thread pool, evita-se o custo de criar e destruir threads repetidamente, pois as mesmas threads podem ser reutilizadas para múltiplas tarefas.

Note
Nota

Se houver mais tarefas do que threads, as tarefas aguardam na Task Queue. Uma tarefa da fila é processada por uma thread disponível do pool, e, assim que a tarefa é concluída, a thread busca uma nova tarefa na fila. Quando todas as tarefas da fila são finalizadas, as threads permanecem ativas e aguardam por novas tarefas.

Exemplo do Cotidiano

Pense em um restaurante onde cozinheiros (threads) preparam pedidos (tarefas). Em vez de contratar um novo cozinheiro para cada pedido, o restaurante emprega um número limitado de cozinheiros que atendem aos pedidos conforme eles chegam. Assim que um cozinheiro termina um pedido, ele assume o próximo, o que contribui para o uso eficiente dos recursos do restaurante.

Método Principal

newFixedThreadPool(int n): Cria um pool com um número fixo de threads igual a n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Cria um pool que pode criar novas threads conforme necessário, mas irá reutilizar threads disponíveis se houver.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Cria um pool de thread único que garante que as tarefas sejam executadas sequencialmente, ou seja, uma após a outra. Útil para tarefas que precisam ser executadas em ordem estrita.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

Em todos os exemplos, os métodos de Executors retornam uma implementação da interface ExecutorService, que é utilizada para gerenciar threads.

ExecutorService fornece métodos para gerenciar um pool de threads. Por exemplo, submit(Runnable task) aceita uma tarefa como um objeto Runnable e a coloca em uma fila para execução. Ele retorna um objeto Future, que pode ser utilizado para verificar o status da tarefa e obter um resultado caso a tarefa produza um resultado.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

O método shutdown() inicia um desligamento gracioso do pool de threads. Ele deixa de aceitar novas tarefas, mas irá concluir as tarefas atuais. Após chamar este método, o pool não pode ser reiniciado.

O método awaitTermination(long timeout, TimeUnit unit) aguarda que todas as tarefas no pool sejam finalizadas dentro do período de tempo especificado. Trata-se de uma espera bloqueante que permite garantir que todas as tarefas sejam concluídas antes de finalizar o pool.

Além disso, não mencionamos a principal interface que auxilia no acompanhamento do estado da thread, que é a interface Future. O método submit() da interface ExecutorService retorna uma implementação da interface Future.

Se for necessário obter o resultado da execução da thread, pode-se utilizar o método get(). Se a thread implementar Runnable, o método get() não retorna nada, mas se for Callable<T>, retorna o tipo T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Também é possível utilizar o método cancel(boolean mayInterruptIfRunning) para tentar cancelar a execução de uma tarefa. Se a tarefa ainda não tiver iniciado, ela será cancelada. Se a tarefa já estiver em execução, ela pode ser interrompida com base no parâmetro mayInterruptIfRunning.

true: Se a tarefa estiver em execução, ela será interrompida por meio da chamada de Thread.interrupt() na thread em execução. false: Se a tarefa estiver em execução, ela não será interrompida e a tentativa de cancelamento não terá efeito sobre a tarefa em andamento.

Bem como 2 métodos que intuitivamente indicam sua finalidade:

  • isCancelled(): Verifica se a tarefa foi cancelada;
  • isDone(): Verifica se a tarefa foi concluída.

Exemplo de Uso

Note
Nota

O tamanho do pool de threads depende da natureza das tarefas executadas. Normalmente, o tamanho do pool de threads não deve ser fixo; em vez disso, deve ser personalizável. O tamanho ideal é determinado pelo monitoramento da taxa de processamento das tarefas executadas.

É mais eficiente utilizar o número de threads = processor cores. Isso pode ser observado no código utilizando Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Diferenças entre Criar Threads Diretamente e Utilizar ExecutorService

As principais diferenças entre criar threads diretamente e utilizar ExecutorService são conveniência e gerenciamento de recursos. A criação manual de threads exige o gerenciamento individual de cada thread, o que complica o código e a administração.

O ExecutorService simplifica o gerenciamento ao utilizar um pool de threads, facilitando o tratamento das tarefas. Além disso, enquanto a criação manual de threads pode resultar em alto consumo de recursos, o ExecutorService permite personalizar o tamanho do pool de threads.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 3. Capítulo 6

Pergunte à IA

expand

Pergunte à IA

ChatGPT

Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo

Suggested prompts:

Can you explain more about how ExecutorService manages threads?

What are some best practices for using thread pools?

How does the Future interface help in managing task results?

Awesome!

Completion rate improved to 3.33

bookExecutores e Pool de Threads

Deslize para mostrar o menu

Já exploramos uma variedade de mecanismos para suportar multithreading, e Executors é um deles!

O que são Executors e Thread Pooling?

Executors é um mecanismo que oferece abstrações de alto nível para o gerenciamento de threads. Permite criar e gerenciar um pool de threads, que consiste em um conjunto de threads pré-existentes prontas para executar tarefas. Em vez de criar uma nova thread para cada tarefa, as tarefas são enviadas para o pool, onde sua execução é distribuída entre as threads.

Então, o que exatamente é um thread pool? Trata-se de uma coleção de threads pré-existentes prontas para executar tarefas. Ao utilizar um thread pool, evita-se o custo de criar e destruir threads repetidamente, pois as mesmas threads podem ser reutilizadas para múltiplas tarefas.

Note
Nota

Se houver mais tarefas do que threads, as tarefas aguardam na Task Queue. Uma tarefa da fila é processada por uma thread disponível do pool, e, assim que a tarefa é concluída, a thread busca uma nova tarefa na fila. Quando todas as tarefas da fila são finalizadas, as threads permanecem ativas e aguardam por novas tarefas.

Exemplo do Cotidiano

Pense em um restaurante onde cozinheiros (threads) preparam pedidos (tarefas). Em vez de contratar um novo cozinheiro para cada pedido, o restaurante emprega um número limitado de cozinheiros que atendem aos pedidos conforme eles chegam. Assim que um cozinheiro termina um pedido, ele assume o próximo, o que contribui para o uso eficiente dos recursos do restaurante.

Método Principal

newFixedThreadPool(int n): Cria um pool com um número fixo de threads igual a n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Cria um pool que pode criar novas threads conforme necessário, mas irá reutilizar threads disponíveis se houver.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Cria um pool de thread único que garante que as tarefas sejam executadas sequencialmente, ou seja, uma após a outra. Útil para tarefas que precisam ser executadas em ordem estrita.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

Em todos os exemplos, os métodos de Executors retornam uma implementação da interface ExecutorService, que é utilizada para gerenciar threads.

ExecutorService fornece métodos para gerenciar um pool de threads. Por exemplo, submit(Runnable task) aceita uma tarefa como um objeto Runnable e a coloca em uma fila para execução. Ele retorna um objeto Future, que pode ser utilizado para verificar o status da tarefa e obter um resultado caso a tarefa produza um resultado.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

O método shutdown() inicia um desligamento gracioso do pool de threads. Ele deixa de aceitar novas tarefas, mas irá concluir as tarefas atuais. Após chamar este método, o pool não pode ser reiniciado.

O método awaitTermination(long timeout, TimeUnit unit) aguarda que todas as tarefas no pool sejam finalizadas dentro do período de tempo especificado. Trata-se de uma espera bloqueante que permite garantir que todas as tarefas sejam concluídas antes de finalizar o pool.

Além disso, não mencionamos a principal interface que auxilia no acompanhamento do estado da thread, que é a interface Future. O método submit() da interface ExecutorService retorna uma implementação da interface Future.

Se for necessário obter o resultado da execução da thread, pode-se utilizar o método get(). Se a thread implementar Runnable, o método get() não retorna nada, mas se for Callable<T>, retorna o tipo T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Também é possível utilizar o método cancel(boolean mayInterruptIfRunning) para tentar cancelar a execução de uma tarefa. Se a tarefa ainda não tiver iniciado, ela será cancelada. Se a tarefa já estiver em execução, ela pode ser interrompida com base no parâmetro mayInterruptIfRunning.

true: Se a tarefa estiver em execução, ela será interrompida por meio da chamada de Thread.interrupt() na thread em execução. false: Se a tarefa estiver em execução, ela não será interrompida e a tentativa de cancelamento não terá efeito sobre a tarefa em andamento.

Bem como 2 métodos que intuitivamente indicam sua finalidade:

  • isCancelled(): Verifica se a tarefa foi cancelada;
  • isDone(): Verifica se a tarefa foi concluída.

Exemplo de Uso

Note
Nota

O tamanho do pool de threads depende da natureza das tarefas executadas. Normalmente, o tamanho do pool de threads não deve ser fixo; em vez disso, deve ser personalizável. O tamanho ideal é determinado pelo monitoramento da taxa de processamento das tarefas executadas.

É mais eficiente utilizar o número de threads = processor cores. Isso pode ser observado no código utilizando Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Diferenças entre Criar Threads Diretamente e Utilizar ExecutorService

As principais diferenças entre criar threads diretamente e utilizar ExecutorService são conveniência e gerenciamento de recursos. A criação manual de threads exige o gerenciamento individual de cada thread, o que complica o código e a administração.

O ExecutorService simplifica o gerenciamento ao utilizar um pool de threads, facilitando o tratamento das tarefas. Além disso, enquanto a criação manual de threads pode resultar em alto consumo de recursos, o ExecutorService permite personalizar o tamanho do pool de threads.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 3. Capítulo 6
some-alt