Concorrência e Multithreading em Java: do básico às técnicas avançadas
Aplicações modernas exigem desempenho máximo. Em Java, concorrência e multithreading são ferramentas-chave para o uso eficiente de processadores multi-core. Neste artigo, vamos analisar como trabalhar corretamente com threads, sincronização e utilitários modernos de concorrência.
O que é multithreading em Java?
Multithreading é a capacidade de um programa executar várias tarefas simultaneamente. Em Java, cada thread é um processo leve, com sua própria pilha de chamadas. Principais vantagens:
- Aumento de desempenho através da execução paralela
- Interface de usuário mais responsiva
- Uso eficiente dos recursos da CPU
Criação de threads: duas abordagens
Em Java, existem duas maneiras principais de criar uma thread:
1. Herança da classe Thread
public class MyThread extends Thread { @Override public void run() { System.out.println("Thread executando: " + Thread.currentThread().getName()); }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Iniciar a thread }}2. Implementação da interface Runnable
public class Task implements Runnable { @Override public void run() { System.out.println("Tarefa executando na thread: " + Thread.currentThread().getName()); }
public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); }}Recomenda-se usar Runnable, pois não limita a possibilidade de herdar outras classes.
Sincronização de threads: evitando race condition
Quando várias threads acessam dados compartilhados, surgem problemas de sincronização. Vamos considerar um exemplo clássico — um contador:
public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}A palavra-chave synchronized garante que apenas uma thread possa executar o método por vez. Uma alternativa é usar Lock do pacote java.util.concurrent.locks.
Deadlock: como evitar o bloqueio mútuo
Deadlock ocorre quando duas threads esperam pela liberação de recursos uma da outra. Exemplo:
public class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object();
public void method1() { synchronized (lock1) { synchronized (lock2) { // Seção crítica } } }
public void method2() { synchronized (lock2) { synchronized (lock1) { // Seção crítica } } }}Para evitar deadlock, sempre adquira os locks na mesma ordem ou use tryLock() com timeout.
ExecutorService: gerenciamento de pool de threads
Criar threads manualmente é ineficiente. O Java fornece ExecutorService para gerenciar um pool de threads:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("Executando em: " + Thread.currentThread().getName()); }); }
executor.shutdown(); // Finalizar após executar todas as tarefas }}Principais t