Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.


  1. Java oferece Callable<V> (como Runnable mas com retorno) e Future<V> (que representa o resultado pendente) para lidar com retorno de threads de maneira mais robusta. ↩