Concorrência e Multithreading em Java: Nível Avançado
\n\nMultithreading em Java é um dos tópicos mais poderosos e, ao mesmo tempo, complexos. No nível básico, usamos synchronized e Thread, mas para construir sistemas performáticos e escaláveis, é necessário um entendimento mais profundo. Neste artigo, abordaremos mecanismos avançados de concorrência: pools de threads, ForkJoinPool, programação assíncrona com CompletableFuture, ajuste fino de locks e gerenciamento de memória. O material é voltado para desenvolvedores que já conhecem os fundamentos do multithreading.
1. Trabalho Avançado com Pools de Threads: ExecutorService e Configuração
\n\nO uso de ExecutorService é o padrão no desenvolvimento profissional. No entanto, o Executors.newFixedThreadPool(10) padrão nem sempre é adequado. Vamos explorar a configuração fina do ThreadPoolExecutor.
import java.util.concurrent.*;\n\npublic class CustomThreadPool {\n public static void main(String[] args) {\n int corePoolSize = 4;\n int maxPoolSize = 8;\n long keepAliveTime = 30;\n TimeUnit unit = TimeUnit.SECONDS;\n BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);\n\n ThreadPoolExecutor executor = new ThreadPoolExecutor(\n corePoolSize,\n maxPoolSize,\n keepAliveTime,\n unit,\n workQueue,\n new ThreadPoolExecutor.CallerRunsPolicy() // política de saturação\n );\n\n // Monitoramento do estado do pool\n executor.submit(() -> {\n System.out.println("Threads ativas: " + executor.getActiveCount());\n System.out.println("Tarefas concluídas: " + executor.getCompletedTaskCount());\n });\n\n executor.shutdown();\n }\n}\n\nParâmetros-chave:
\n- \n
corePoolSize— número mínimo de threads mantidas mesmo sem tarefas. \n maxPoolSize— número máximo de threads. \n keepAliveTime— tempo de vida de threads excedentes. \n workQueue— fila de tarefas.ArrayBlockingQueuecom tamanho limitado evita estouro de memória. \n - Política de saturação — o que fazer quando a fila está cheia e todas as threads estão ocupadas.
CallerRunsPolicydesacelera a thread chamadora. \n
Para tarefas longas de I/O, é melhor aumentar o corePoolSize; para tarefas CPU-bound, baseie-se em Runtime.getRuntime().availableProcessors().
2. ForkJoinPool e Tarefas Recursivas
\n\nForkJoinPool é um pool especializado para tarefas que podem ser divididas em subtarefas (dividir para conquistar). Ele utiliza eficientemente o roubo de trabalho (work stealing).
Exemplo: cálculo da soma dos elementos de um array recursivamente.
\n\nimport java.util.concurrent.RecursiveTask;\nimport java.util.concurrent.ForkJoinPool;\n\npublic class SumTask extends RecursiveTask<Long> {\n private static final int THRESHOLD = 1000;\n private final int[] array;\n private final int start;\n private final int end;\n\n public SumTask(int[] array, int start, int end) {\n this.array = array;\n this.start = start;\n this.end = end;\n }\n\n @Override\n protected Long compute() {\n if (end - start <= THRESHOLD) {\n long sum = 0;\n for (int i = start; i < end; i++) {\n sum += array[i];\n }\n return sum;\n }\n int mid = (start + end) / 2;\n SumTask leftTask = new SumTask(array, start, mid);\n SumTask rightTask = new SumTask(array, mid, end);\n\n leftTask.fork(); // execução assíncrona\n long rightResult = rightTask.compute(); // calcula