Pool de threads Java gerencia o conjunto de threads trabalhadoras. Ele contém uma fila que mantém tarefas aguardando execução. Podemos usar ThreadPoolExecutor
para criar um pool de threads em Java. O pool de threads Java gerencia a coleção de threads Runnable. As threads trabalhadoras executam threads Runnable da fila. java.util.concurrent.Executors fornecem métodos de fábrica e suporte para a interface java.util.concurrent.Executor para criar o pool de threads em Java. Executors é uma classe de utilitários que também fornece métodos úteis para trabalhar com ExecutorService, ScheduledExecutorService, ThreadFactory e classes Callable por meio de vários métodos de fábrica. Vamos escrever um programa simples para explicar seu funcionamento. Primeiro, precisamos ter uma classe Runnable, chamada WorkerThread.java
package com.journaldev.threadpool;
public class WorkerThread implements Runnable {
private String command;
public WorkerThread(String s){
this.command=s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Start. Command = "+command);
processCommand();
System.out.println(Thread.currentThread().getName()+" End.");
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString(){
return this.command;
}
}
Exemplo de ExecutorService
Aqui está a classe do programa de teste SimpleThreadPool.java
, onde estamos criando um pool de threads fixo a partir do framework Executors.
package com.journaldev.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
No programa acima, estamos criando um pool de threads de tamanho fixo com 5 threads trabalhadoras. Em seguida, estamos enviando 10 trabalhos para este pool; como o tamanho do pool é 5, ele começará a trabalhar em 5 trabalhos e os outros trabalhos estarão no estado de espera. Assim que um dos trabalhos for concluído, outro trabalho da fila de espera será escolhido pela thread trabalhadora e será executado. Aqui está a saída do programa acima.
pool-1-thread-2 Start. Command = 1
pool-1-thread-4 Start. Command = 3
pool-1-thread-1 Start. Command = 0
pool-1-thread-3 Start. Command = 2
pool-1-thread-5 Start. Command = 4
pool-1-thread-4 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
pool-1-thread-3 End.
pool-1-thread-3 Start. Command = 8
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 9
pool-1-thread-1 Start. Command = 7
pool-1-thread-5 Start. Command = 6
pool-1-thread-4 Start. Command = 5
pool-1-thread-2 End.
pool-1-thread-4 End.
pool-1-thread-3 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
Finished all threads
A saída confirma que existem cinco threads no pool nomeados de “pool-1-thread-1” a “pool-1-thread-5” e são responsáveis por executar as tarefas submetidas ao pool.
Exemplo de ThreadPoolExecutor
A classe Executors fornece uma implementação simples de ExecutorService usando ThreadPoolExecutor, mas ThreadPoolExecutor oferece muito mais recursos do que isso. Podemos especificar o número de threads que estarão ativas quando criamos uma instância de ThreadPoolExecutor e podemos limitar o tamanho do pool de threads e criar nossa própria implementação de RejectedExecutionHandler para lidar com os trabalhos que não podem ser acomodados na fila de trabalhadores. Aqui está nossa implementação personalizada da interface RejectedExecutionHandler.
package com.journaldev.threadpool;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + " is rejected");
}
}
ThreadPoolExecutor
fornece vários métodos através dos quais podemos descobrir o estado atual do executor, tamanho do pool, contagem de threads ativas e contagem de tarefas. Então eu tenho um thread monitor que imprimirá as informações do executor em determinados intervalos de tempo.
package com.journaldev.threadpool;
import java.util.concurrent.ThreadPoolExecutor;
public class MyMonitorThread implements Runnable
{
private ThreadPoolExecutor executor;
private int seconds;
private boolean run=true;
public MyMonitorThread(ThreadPoolExecutor executor, int delay)
{
this.executor = executor;
this.seconds=delay;
}
public void shutdown(){
this.run=false;
}
@Override
public void run()
{
while(run){
System.out.println(
String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
this.executor.getPoolSize(),
this.executor.getCorePoolSize(),
this.executor.getActiveCount(),
this.executor.getCompletedTaskCount(),
this.executor.getTaskCount(),
this.executor.isShutdown(),
this.executor.isTerminated()));
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Aqui está um exemplo de implementação de pool de threads usando ThreadPoolExecutor.
package com.journaldev.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WorkerPool {
public static void main(String args[]) throws InterruptedException{
Implementação do RejectedExecutionHandler
RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
Obter a implementação da ThreadFactory para usar
ThreadFactory threadFactory = Executors.defaultThreadFactory();
criando o ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), threadFactory, rejectionHandler);
iniciar a thread de monitoramento
MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
submeter trabalho ao pool de threads
for(int i=0; i<10; i++){
executorPool.execute(new WorkerThread("cmd"+i));
}
Thread.sleep(30000);
desligar o pool
executorPool.shutdown();
desligar a thread de monitoramento
Thread.sleep(5000);
monitor.shutdown();
}
}
Observe que ao inicializar o ThreadPoolExecutor, estamos mantendo o tamanho inicial do pool como 2, o tamanho máximo do pool como 4 e o tamanho da fila de trabalho como 2. Portanto, se houver 4 tarefas em execução e mais tarefas forem submetidas, a fila de trabalho conterá apenas 2 delas e o restante será tratado por RejectedExecutionHandlerImpl. Aqui está a saída do programa acima que confirma a declaração acima.
pool-1-thread-1 Start. Command = cmd0
pool-1-thread-4 Start. Command = cmd5
cmd6 is rejected
pool-1-thread-3 Start. Command = cmd4
pool-1-thread-2 Start. Command = cmd1
cmd7 is rejected
cmd8 is rejected
cmd9 is rejected
[monitor] [0/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-4 End.
pool-1-thread-1 End.
pool-1-thread-2 End.
pool-1-thread-3 End.
pool-1-thread-1 Start. Command = cmd3
pool-1-thread-4 Start. Command = cmd2
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-1 End.
pool-1-thread-4 End.
[monitor] [4/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
Observe a alteração no número de tarefas ativas, concluídas e total de tarefas concluídas do executor. Podemos invocar o método shutdown() para concluir a execução de todas as tarefas submetidas e encerrar o pool de threads. Se você deseja agendar uma tarefa para ser executada com atraso ou periodicamente, pode usar a classe ScheduledThreadPoolExecutor. Leia mais sobre eles em Executor de Pool de Threads Agendado Java.