Criação e Execução de Threads em Java
Para criar uma nova thread, existem duas abordagens principais em Java: estender a classe Thread
ou implementar a interface Runnable
.
Abordagem 1: Herança da Classe Thread
A primeira abordagem consiste em criar uma classe que herda diretamente de Thread
e sobrescreve seu método run()
.
class JThread extends Thread {
JThread(String name) {
super(name);
}
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted");
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
new JThread("JThread").start();
System.out.println("Main thread finished...");
}
}
Neste exemplo, a classe JThread
é uma thread. Seu construtor recebe um nome e o repassa para o construtor da superclasse (Thread
). A lógica a ser executada na nova linha de execução é definida dentro do método run()
.
No método main
, a chamada de start()
em uma instância de JThread
instrui a JVM a iniciar uma nova thread. Em seguida, a JVM chama o método run()
dessa thread.
Saída no console:
Main thread started... Main thread finished... JThread started... JThread finished...
Observe que a thread principal (main
) termina sua execução antes da JThread
, demonstrando que elas operam de forma independente.
Da mesma forma, é possível iniciar múltiplas threads:
public static void main(String[] args) {
System.out.println("Main thread started...");
for (int i = 1; i < 6; i++) {
new JThread("JThread " + i).start();
}
System.out.println("Main thread finished...");
}
Saída no console (exemplo):
Main thread started... Main thread finished... JThread 2 started... JThread 5 started... JThread 4 started... JThread 1 started... JThread 3 started... JThread 1 finished... JThread 2 finished... JThread 5 finished... JThread 4 finished... JThread 3 finished...
A ordem de início e término das threads não é garantida, pois depende do escalonador de threads do sistema operacional.
Aguardando a Finalização de uma Thread
Em muitos cenários, é necessário que a thread principal espere a conclusão de outras. Para isso, utiliza-se o método join()
. A thread que chama join()
em outra ficará em estado de espera até que a segunda termine.
public static void main(String[] args) {
System.out.println("Main thread started...");
JThread t = new JThread("JThread");
t.start();
try {
t.join(); // A thread main aguarda a finalização da thread t
} catch (InterruptedException e) {
System.out.printf("%s has been interrupted", t.getName());
}
System.out.println("Main thread finished...");
}
Neste caso, a main thread
só executará a última linha de código após a finalização completa da thread t
.
Saída no console:
Main thread started... JThread started... JThread finished... Main thread finished...
Abordagem 2: Implementação da Interface Runnable
Esta é a abordagem mais recomendada e flexível. Em vez de herdar de Thread
, a classe implementa a interface Runnable
.
Esta prática é preferível por duas razões principais:
- Java não suporta herança múltipla. Se uma classe já precisa herdar de outra, ela não poderá herdar também de
Thread
. - Promove um design de código mais limpo, pois separa a tarefa (o que deve ser feito, definido no
Runnable
) do mecanismo de execução (o objetoThread
que executa a tarefa).
A interface Runnable
define um único método: run()
.
class MyTask implements Runnable {
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted");
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = new MyTask();
Thread myThread = new Thread(task, "MyThread");
myThread.start();
System.out.println("Main thread finished...");
}
}
A lógica de execução é a mesma, mas a criação é diferente. Primeiro, cria-se uma instância da tarefa (MyTask
). Em seguida, esse objeto é passado para o construtor da classe Thread
. É o objeto Thread
que gerencia a execução.
Saída no console:
Main thread started... Main thread finished... MyThread started... MyThread finished...
Como Runnable
é uma interface funcional, sua implementação pode ser simplificada com uma expressão lambda, eliminando a necessidade de uma classe separada para tarefas simples.
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
Runnable task = () -> {
System.out.printf("%s started... \n", Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted");
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
};
Thread myThread = new Thread(task, "MyThread");
myThread.start();
System.out.println("Main thread finished...");
}
}
Resumo
- Existem duas formas de criar threads: herdando da classe
Thread
ou implementando a interfaceRunnable
. - A implementação de
Runnable
é geralmente preferível por ser mais flexível e promover um melhor design de código. - O método
start()
inicia uma nova linha de execução, que por sua vez executa o código contido no métodorun()
. - O método
join()
é usado para fazer com que uma thread espere pela finalização de outra. - Expressões lambda oferecem uma forma concisa de criar implementações de
Runnable
.