A abordagem mais direta para criar uma thread em Java é estender a classe Thread e sobrescrever o método run(). A instância criada deve ser iniciada com start().
class MinhaThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("ID da thread: " + Thread.currentThread().getId());
}
}
MinhaThread t = new MinhaThread();
t.start();
Implementando a Interface Runnable
Em vez de herdar de Thread, você pode implementar Runnable e passar a instância para o construtor de Thread. Essa abordagem é preferível quando sua classe já herda de outra classe, pois Java não suporta herança múltipla.
class TarefaRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("ID da thread: " + Thread.currentThread().getId());
}
}
Thread t = new Thread(new TarefaRunnable());
t.start();
Utilizando Callable com FutureTask
A interface Callable é uma alternativa a Runnable que permite retornar um valor e lançar exceções verificadas. O método call() substitui run(). Para executar uma Callable, é necessário envolvê-la em um FutureTask.
import java.util.concurrent.*;
class TarefaCallable implements Callable<String> {
@Override
public String call() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("ID da thread: " + Thread.currentThread().getId());
return "Resultado da execução";
}
}
FutureTask<String> tarefa = new FutureTask<>(new TarefaCallable());
new Thread(tarefa).start();
// get() bloqueia até o resultado estar disponível ou o timeout expirar
String resultado = tarefa.get(3, TimeUnit.SECONDS);
System.out.println(resultado);
Gerenciamento de Threads com ThreadPoolExecutor
O Java fornece a classe utilitária Executors para criar pools de threads, porém as diretrizes de codificação da Alibaba proíbem seu uso. O motivo é que LinkedBlockingQueue, sem um limite definido, pode crescer até o valor máximo de Integer, resultando em OutOfMemoryError. Recomenda-se utilizar ThreadPoolExecutor diretamente, definindo explicitamente o tamanho da fila de bloqueio.
Parâmetros do Construtor do ThreadPoolExecutor
- corePoolSize: número mínimo de threads mantidas no pool, mesmo sem tarefas em execução. Threads adicionais além desse limite só são criados quando a fila de trabalho está cheia.
- maximumPoolSize: número máximo de threads que o pool pode conter. Pode ser alterado após a criação com
setMaximumPoolSize(). - keepAliveTime: tempo máximo que uma thread ociosa pode aguardar antes de ser finalizada, quando o pool possui mais threads que o
corePoolSize. - unit: unidade de tempo do parâmetro
keepAliveTime. - workQueue: fila que armazena tarefas aguardando execução. É obrigatório definir um tamanho para evitar
OOM. - threadFactory: fábrica responsável por criar novas threads.
- RejectedExecutionHandler: estratégia aplicada quando uma tarefa é rejeitada pelo pool.
Fluxo de Uso
- Crie uma classe que implemente
RunnableouCallable. - Instancie o
ThreadPoolExecutorcom os parâmetros apropriados. - Envie tarefas ao pool usando
submit(), que aceitaRunnableouCallable. ParaRunnable, é possível passar um resultado opcional como segundo argumento. - Recupere o resultado da execução através de
Future.get(). Essa chamada é bloqueante. - Encerre o pool com
shutdown()oushutdownNow(). O primeiro bloqueia novas submissões, aguarda a conclusão das tarefas em execução e descarta as não iniciadas. O segundo tenta interromper todas as threads em execução, podendo lançar exceções.
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,
10,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10)
);
Future<?> futuro = pool.submit(new TarefaCallable());
System.out.println(futuro.get());
pool.shutdown();