Oi! Hoje continuaremos a falar sobre multithreading. Vamos examinar a classe Thread e o que alguns de seus métodos fazem. Quando estudamos métodos de classe anteriormente, geralmente apenas escrevemos isto: <nome do método> -> <o que o método faz>. Isso não funcionará com
Thread
os métodos de :) Eles têm uma lógica mais complexa que você não conseguirá descobrir sem alguns exemplos.
O método Thread.start()
Vamos começar nos repetindo. Como você provavelmente se lembra, você pode criar um thread fazendo sua classe herdar aThread
classe e substituindo o run()
método. Mas não vai começar sozinho, é claro. Para fazer isso, chamamos start()
o método do nosso objeto. Vamos relembrar o exemplo da lição anterior:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Observação: para iniciar um thread, você deve chamar ostart()
método especial em vez dorun()
método! Este é um erro fácil de cometer, especialmente quando você começa a estudar multithreading. Em nosso exemplo, se você chamar orun()
método 10 vezes em vez destart()
, obterá isto:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
Veja os resultados do nosso programa: Tópico executado: Tópico-0 Tópico executado: Tópico-1 Tópico executado: Tópico-2 Tópico executado: Tópico-3 Tópico executado: Tópico-4 Tópico executado: Tópico-5 Tópico executado: Tópico-6 Thread executado: Thread-7 Thread executado: Thread-8 Thread executado: Thread-9 Observe a ordem da saída: Tudo está acontecendo em perfeita ordem. Estranho, né? Não estamos acostumados com isso, pois já sabemos que a ordem em que as threads são iniciadas e executadas é determinada por um intelecto superior dentro do nosso sistema operacional: o agendador de threads. Talvez tenhamos tido sorte? Claro, não se trata de sorte. Você pode verificar isso executando o programa mais algumas vezes. A questão é que chama orun()
método diretamente não tem nada a ver com multithreading. Neste caso, o programa será executado na thread principal, a mesma thread que executa o main()
método. Basta imprimir sucessivamente 10 linhas no console e pronto. 10 tópicos não foram iniciados. Portanto, lembre-se disso no futuro e verifique-se constantemente. Se você deseja que o run()
método seja chamado, chame start()
. Vamos mais longe.
O método Thread.sleep()
Para suspender a execução do thread atual por um tempo, usamos osleep()
método. O sleep()
método leva um número de milissegundos como argumento, o que indica a quantidade de tempo para colocar o thread em hibernação.
public class Main {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
System.out.println(" - How long did I sleep? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " seconds");
}
}
Saída do console: - Quanto tempo eu dormi? - 3 segundos Nota: o sleep()
método é estático: ele dorme o thread atual. Ou seja, aquele que está sendo executado atualmente. Aqui está outro ponto importante: um fio adormecido pode ser interrompido. Nesse caso, o programa lança um arquivo InterruptedException
. Vamos considerar um exemplo abaixo. A propósito, o que acontece depois que o thread acorda? Ele continuará a ser executado exatamente de onde parou? Não. Depois que um thread é ativado, ou seja, o tempo passado como um argumento para Thread.sleep()
passou, ele faz a transição para executávelestado. Mas isso não significa que o agendador de threads irá executá-lo. Ele pode muito possivelmente dar preferência a algum outro fio não adormecido e permitir que nosso fio recém-desperto continue seu trabalho um pouco mais tarde. Lembre-se disso: acordar não significa continuar o trabalho imediatamente!
O método Thread.join()
Ojoin()
método suspende a execução do thread atual até que outro thread termine. Se temos 2 threads, t1
e t2
, e escrevemos
t1.join()
então t2
não iniciará até que t1
tenha terminado seu trabalho. O join()
método pode ser usado para garantir a ordem de execução dos threads. Vamos considerar como o join()
método funciona no exemplo a seguir:
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Thread started: " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + getName() + " is finished.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/* The second thread (t2) will start running only after the first thread (t1)
is finished (or an exception is thrown) */
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// The main thread will continue running only after t1 and t2 have finished
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All threads have finished. The program is finished.");
}
}
Criamos uma ThreadExample
classe simples. Sua tarefa é exibir uma mensagem de que o thread foi iniciado, adormecer por 5 segundos e, finalmente, relatar que o trabalho foi concluído. Pedaco de bolo. A lógica principal está na Main
classe. Veja os comentários: usamos o join()
método para gerenciar com sucesso a ordem de execução dos threads. Se você se lembra de como começamos este tópico, a ordem de execução é tratada pelo agendador de threads. Ele executa threads a seu próprio critério: cada vez de uma maneira diferente. Aqui estamos usando o método para garantir que o t1
thread será iniciado e executado primeiro, depois ot2
thread, e somente depois disso o thread principal do programa continuará. Se movendo. Em programas reais, muitas vezes você encontrará situações em que precisará interromper a execução de um thread. Por exemplo, nossa thread está em execução, mas está esperando por um determinado evento ou condição. Se isso ocorrer, o encadeamento será interrompido. Provavelmente faria sentido se houvesse algum tipo de stop()
método. Mas não é tão simples. Era uma vez, o Java realmente tinha um Thread.stop()
método e permitia que um thread fosse interrompido. Mas foi posteriormente removido da biblioteca Java. Você pode encontrá-lo na documentação do Oracle e ver que está marcado como obsoleto. Por que? Porque simplesmente parou o thread sem fazer mais nada. Por exemplo, o encadeamento pode estar trabalhando com dados e alterando algo. Então, no meio de seu trabalho, foi cortado abruptamente e sem cerimônia pelo stop()
método. Sem um desligamento adequado, nem liberação de recursos, nem mesmo tratamento de erros — não havia nada disso. Para exagerar um pouco, o stop()
método simplesmente destruiu tudo em seu caminho. Foi como puxar o cabo de alimentação da tomada para desligar o computador. Sim, você pode obter o resultado desejado. Mas todo mundo sabe que depois de algumas semanas o computador não vai te agradecer por tratá-lo dessa forma. É por isso que a lógica para interromper threads mudou em Java e agora usa um interrupt()
método especial.
O método Thread.interrupt()
O que acontece se ointerrupt()
método for chamado em um thread? Existem 2 possibilidades:
- Se o objeto estava no estado de espera, por exemplo, devido aos métodos
join
ousleep
, a espera será interrompida e o programa lançará umInterruptedException
. - Se o thread estiver em um estado de funcionamento, o
interrupted
sinalizador booleano será definido no objeto.
Thread
classe tem o boolean isInterrupted()
método. Voltemos ao exemplo do relógio que estava em uma aula do curso básico. Por conveniência, simplificamos um pouco:
public class Clock extends Thread {
public static void main(String[] args) throws InterruptedException {
Clock clock = new Clock();
clock.start();
Thread.sleep(10000);
clock.interrupt();
}
public void run() {
Thread current = Thread.currentThread();
while (!current.isInterrupted())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("The thread was interrupted");
break;
}
System.out.println("Tick");
}
}
}
Neste caso, o relógio é iniciado e começa a contar a cada segundo. No décimo segundo, interrompemos o fio do relógio. Como você já sabe, se o thread que estamos tentando interromper estiver em um dos estados de espera, o resultado será um arquivo InterruptedException
. Esta é uma exceção verificada, então podemos capturá-la facilmente e executar nossa lógica para terminar o programa. E foi exatamente isso que fizemos. Aqui está nosso resultado: Tick Tick Tick Tcik Tick Tick Tick Tick Tick O thread foi interrompido Isso conclui nossa introdução aos Thread
métodos mais importantes da classe. Boa sorte!
GO TO FULL VERSION