Concorrência e Multithreading em Java: da teoria à prática
No mundo moderno do software, o desempenho das aplicações depende diretamente da capacidade de usar eficientemente os recursos do processador. Concorrência e multithreading em Java são conceitos-chave que permitem executar várias tarefas simultaneamente, acelerar o processamento de dados e criar interfaces de usuário responsivas. Neste artigo, vamos explorar os principais mecanismos de multithreading em Java, aprender a criar e gerenciar threads, e discutir as melhores práticas para evitar erros comuns.
O que é uma thread e como criá-la em Java?
Uma thread é um processo leve que é executado dentro de uma única aplicação. Cada thread tem sua própria pilha de chamadas e pode operar independentemente de outras threads. Em Java, existem duas maneiras principais de criar uma thread.
Método 1: Herdando da classe Thread
public class MyThread extends Thread { @Override public void run() { System.out.println("Thread " + getName() + " iniciada!"); }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Iniciar a thread }}Método 2: Implementando a interface Runnable
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("Thread executando via Runnable"); }
public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); }}Recomenda-se usar o segundo método, pois ele não requer herança e permite implementar várias interfaces simultaneamente.
Sincronização de threads: evitando race condition
Quando várias threads acessam dados compartilhados ao mesmo tempo, ocorre uma condição de corrida (race condition). Para evitar isso, o Java fornece mecanismos de sincronização.
Palavra-chave synchronized
Bloqueia o acesso a um método ou bloco de código para outras threads até que a thread atual termine a execução:
public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}Bloqueios com Lock
Um mecanismo mais flexível que synchronized. Permite definir timeouts e tentar adquirir um bloqueio sem bloquear:
import java.util.concurrent.locks.ReentrantLock;
public class SafeCounter { private final ReentrantLock lock = new ReentrantLock(); private int count = 0;
public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // Sempre liberar o bloqueio } }}Pool de threads e ExecutorService
Criar uma nova thread para cada tarefa é uma operação cara. Para gerenciar threads de forma eficiente, use ExecutorService do pacote java.util.concurrent.
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolExample { public static void main(String[] args) { // Criar um pool de 4 threads ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Tarefa " + taskId + " executando em " + Thread.currentThread().getName()); }); }
executor.shutdown(); // Finalizar o pool após executar todas as tarefas }}Principais tipos de pools:
newFixedThreadPool(n)— número fixo de threads
```