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 objetoThreadque 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
Threadou 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.