ThreadPoolExecutor – Exemplo de Pool de Threads em Java

Java thread pool gerencia o conjunto de threads trabalhadoras. Ele contém uma fila que mantém tarefas esperando para serem executadas. Podemos usar ThreadPoolExecutor para criar um pool de threads em Java. O pool de threads em 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 de suporte para a interface java.util.concurrent.Executor para criar o pool de threads em Java. Executors é uma classe utilitária 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 como funciona. 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 de 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 submetendo 10 trabalhos a este pool, como o tamanho do pool é 5, ele começará a trabalhar em 5 trabalhos e outros trabalhos ficarão em 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 há 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 do ExecutorService usando ThreadPoolExecutor, mas ThreadPoolExecutor fornece 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 caber 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 usando os quais podemos descobrir o estado atual do executor, tamanho do pool, contagem de threads ativas e contagem de tarefas. Então, tenho um thread monitor que imprimirá as informações do executor em intervalos de tempo específicos.

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á o exemplo de implementação do 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();
        Obtenha a implementação ThreadFactory para usar
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        criando o ThreadPoolExecutor
        ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), threadFactory, rejectionHandler);
        inicie a thread de monitoramento
        MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
        Thread monitorThread = new Thread(monitor);
        monitorThread.start();
        submeta o trabalho ao pool de threads
        for(int i=0; i<10; i++){
            executorPool.execute(new WorkerThread("cmd"+i));
        }
        
        Thread.sleep(30000);
        desligue o pool
        executorPool.shutdown();
        desligue 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 manterá apenas 2 delas e o restante será tratado pelo RejectedExecutionHandlerImpl. Aqui está a saída do programa acima que confirma a declaração anterior.

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 mudança no número de tarefas ativas, completadas e total de tarefas concluídas do executor. Podemos invocar o método shutdown() para finalizar 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, então você pode usar a classe ScheduledThreadPoolExecutor. Leia mais sobre eles em Executor de Threads Agendado Java.

Source:
https://www.digitalocean.com/community/tutorials/threadpoolexecutor-java-thread-pool-example-executorservice