Java (java.lang.Thread)
Em Java, threads são objetos da classe java.lang.Thread. O garbage collector
cuida da memória e a tipagem forte cuida dos argumentos. Em compensação, Java
traz suas próprias pegadinhas.
Criando uma thread
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("Hello from thread!");
});
t.start();
t.join();
}
}
Código 4-13: Criando uma thread em Java.
O construtor de Thread recebe um Runnable (uma interface funcional
com um único método run()). Aqui usamos uma lambda, mas qualquer classe que
implementa Runnable serve. Instanciar o objeto Thread não cria a thread
do SO; isso só acontece no start(). Essa separação permite configurar nome,
prioridade e modo daemon antes de iniciar.
A restrição de effectively final
Vamos criar 10 threads:
public class Main {
public static void main(String[] args) throws InterruptedException {
int n = 10;
Thread[] threads = new Thread[n];
for (int i = 0; i < n; i++) {
final int id = i;
threads[i] = new Thread(() -> {
System.out.println("Hello from thread " + id + "!");
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
}
}
Código 4-14: Criando múltiplas threads em Java.
O final int id = i está ali porque lambdas em Java só capturam variáveis
locais que são effectively final, cujo valor não muda após a
inicialização. A variável de loop i muda a cada iteração, então usá-la
direto na lambda causa:
error: local variables referenced from a lambda expression
must be final or effectively final
O compilador barra antes de rodar. Pode parecer restritivo, mas essa restrição
evita toda uma classe de bugs: se o programador quer que a lambda use o valor
de i, ele é forçado a criar uma cópia explícita.
$ javac Main.java && java Main
Hello from thread 0!
Hello from thread 2!
Hello from thread 1!
Hello from thread 4!
Hello from thread 5!
Hello from thread 3!
Hello from thread 6!
Hello from thread 8!
Hello from thread 7!
Hello from thread 9!
run() vs start()
Vamos ver o que acontece quando chamamos run() ao invés de start():
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
System.out.println("--- com run() ---");
t.run();
System.out.println("--- com start() ---");
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
t2.start();
t2.join();
}
}
Código 4-15: run() vs start(): a thread que executa é diferente.
$ javac Main.java && java Main
--- com run() ---
main
--- com start() ---
Thread-1
Com run(), quem executou o código foi a thread main. Nenhuma thread nova
foi criada. O programa roda sequencialmente e produz a saída “correta”,
mas sem concorrência nenhuma.
Threads daemon
Quando uma thread é criada, é possível configurá-la como uma thread daemon. A
JVM só encerra quando todas as threads não-daemon terminam. Se uma thread
não-daemon ainda estiver executando quando a main retornar, o processo fica
bloqueado aguardando a finalização:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> {
try { Thread.sleep(5000); } catch (InterruptedException e) {}
System.out.println("Thread finalizou!");
});
t.start();
System.out.println("main retornou");
}
}
$ javac Main.java && java Main
main retornou
Thread finalizou!
A main terminou, mas o programa continuou rodando por mais 5 segundos até
a thread terminar. Marcando a thread como daemon com t.setDaemon(true)
antes do start(), a JVM não espera:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> {
try { Thread.sleep(5000); } catch (InterruptedException e) {}
System.out.println("Thread finalizou!");
});
t.setDaemon(true);
t.start();
System.out.println("main retornou");
}
}
$ javac Main.java && java Main
main retornou
A mensagem Thread finalizou! nunca apareceu. A JVM encerrou junto com a
main, matando a thread daemon no meio do sleep.
Retornando valores
Runnable não retorna valores. Um truque comum é usar um array de um
elemento como recipiente:
public class Main {
public static void main(String[] args) throws InterruptedException {
int[] answer = {0};
Thread t = new Thread(() -> {
answer[0] = 7 * 7;
});
t.start();
t.join();
System.out.println("7² = " + answer[0]);
}
}
Código 4-16: Retornando um valor de uma thread em Java.
Por que não uma variável int direto? Pela mesma restrição de effectively
final: a lambda não pode modificar uma variável local primitiva. O array
contorna isso porque a referência ao array não muda (satisfaz a restrição),
mas o conteúdo pode ser alterado1.
-
Java oferece
Callable<V>(comoRunnablemas com retorno) eFuture<V>(que representa o resultado pendente) para lidar com retorno de threads de maneira mais robusta. ↩