Java com café: Threads - Java

Threads - Java

Olá, tudo bem?

Hoje vou tratar de um assunto muito interessante: Threads. Mas antes quero comentar outra coisa: Tenho desenvolvido uma forma bem legal de estudar para a certificação e creio que possa se aplicar a outros campos também. Funciona da seguinte maneira: Você vai lendo até chegar numa parte que o assunto ficou cansativo, então pule para outro capítulo que ache interessante. Isso proporciona uma visão global do assunto quando voltar aos primeiros capítulos e te mantém interessado nos estudos.

Mas vamos ao que interessa. Pra começar, o que são threads???

Threads são linhas de execução de um processo. É a maneira de um processo se dividir em duas ou mais partes para que possam ser executadas "ao mesmo tempo". Nem sempre é ao mesmo tempo porque na maioria das vezes temos somente uma CPU disponível. Então alguém fica a cargo de dividir as fatias de tempo entre os concorrentes. Embora quase sempre, mesmo com apenas uma CPU, temos a impressão de que tudo está sendo feito ao mesmo tempo. Mas não está! A CPU está processando um pouco de cada coisa pra te dar essa impressão. Este assunto é bem vasto, mas vamos nos ater aos threads.
Um processo no contexto que estou falando seria o programa. Poderíamos desejar que o programa faça várias coisas ao mesmo tempo. Por exemplo, um browser: Quando o browser carrega uma página com muitas imagens, ele se conecta a vários links para baixar as imagens ao mesmo tempo. Ele não abre uma por uma, existe um thread para cada tarefa de forma que todas são executadas ao mesmo tempo. Lembre-se que "ao mesmo tempo" nem sempre é verdade, mas vamos acreditar que seja.
Em Java, podemos implementar threads instanciando a classe java.lang.Thread. Um objeto do tipo Thread é um objeto como qualquer outro, possui métodos e variáveis. A grande diferença é que ele pussui sua própria pilha de chamada (falei da pilha nesse post sobre construtores). O método main é responsável por iniciar todo o processamento, portanto ele é considerado o thread principal.
As perguntas sobre thread no exame são consideradas a mais difíceis! Portanto é bom praticar muito.
Vamos ver um pouco de ação:
class MyRunnable implements Runnable /*ou extends Thread*/{

public void run() {
  System.out.println("Rodando meu Thread");
}

}

Como você pode ver, temos uma classe que implementa Runnable. Existem duas formas de se trabalhar com threads, implementando Runnable ou estendendo Thread. Você deve conhecer estas duas formas para o exame, mas geralmente a melhor prática é implementar, pois assim fica livre para estender outra classe.
Quando implementamos Runnable devemos sobrescrever o método run. Dentro do método run colocamos todo o trabalho que desejamos executar concorrentemente. Se por acaso tivéssemos escolhido estender Thread, o método run deveria ser sobrescrito da mesma forma.
Mas esta classe não faz nada ainda. Apenas estamos dizendo que o código dentro de run deverá ser executado por um thread. Agora realmente vamos ter um pouco de ação:
public class MyThread {

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  Thread t = new Thread(mr);
  t.start();

}
}

Em uma outra classe instanciamos MyRunnable. Criamos um objeto tipo Thread e passamos como parâmetro a variável que faz referência a MyRunnable. Então iniciamos o thread. O resultado é:
Rodando meu Thread.

Sempre devemos deixar o código do trabalho a ser feito pelo thread separado do código que vai chamá-lo, da maneira como foi mostrado. Em uma classe escrevemos o que era pra ser feito, em outra pedimos pra fazer.
Também é importante lembrar que o thread sempre espera que sua classe tenha um método run sem parâmetros. Se este método for sobrecarregado, o thread não vai chamá-lo, e mesmo que você chame-o, a execução não será a esperada de um thread. A execução acontecerá na mesma pilha de main.


Instanciando um thread

Podemos instanciar um thread de duas formas. A primeira acontece quando estendemos Thread:
class MyRunnable extends Thread{

public void run() {
  System.out.println("Rodando meu Thread");
}
}

public class MyThread{

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  //Thread t = new Thread(mr);
  mr.start();

}
}

Veja que esta forma é mais simples, pois MyRunnable já estendeu Thread. Então precisamos somente instanciar para executar. Mas temos implicações, pois não podemos estender outras classes. A segunda forma é a que foi mostrada primeiramente, onde implementamos Runnable. Primeiro precisamos instanciar MyRunnable e depois Thread:
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);

Também poderíamos passar MyRunnable para diferentes threads, de modo que todos eles executem a mesma tarefa:
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);

t1.start();
t2.start();
t3.start();

Outra observação válida para o exame é que podemos passar um thread ao construtor de outro thread. Suponhamos que neste caso MyRunnable estende Thread, portanto ela É-UM thread. Então podemos passá-la a um construtor de outro thread:
Thread t = new Thread(new MyRunnable());

Isso é possível porque Thread possui vários construtores sobrecarregados e um deles aceita objetos Runnable. Então como Thread implementa Runnable, podemos passar tanto um Runnable quanto um Thread. Mas passar um Thread é totalmente desnecessário porque cria um novo thread sendo que o ideal é passar somente um Runnable.
Tenha em mente também que chamar o método run diretamente de uma classe que implementa Runnable é válido, porém não significa que vai se comportar como um thread:
MyRunnable mr = new MyRunnable();
mr.run();

A chamada entrará na pilha local, pois sempre que quisermos executar com thread devemos instanciar Thread. Lembre-se que o método que inicia o thread é start() e não run.
Até agora estamos indo bem, mas não tiramos proveito do recurso em momento algum. Como saberemos se os threads estão sendo executados ao mesmo tempo? Como poderemos ter certeza que eles não estão sendo executados um após o outro em sequência? Vamos modificar a classe MyRunnable:
class MyRunnable implements Runnable{
public void run() {
  System.out.println("Meu nome é: " + Thread.currentThread().getName());
}
}

Agora cada thread que executar terá que mostrar seu nome. Não se preocupe em não ter uma instância de Thread na classe pois o método chamado é estático. Ele retorna uma referência ao thread em execução e depois pega o nome dele. Agora vamos executar:
public class MyThread{

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  Thread t1 = new Thread(mr);
  Thread t2 = new Thread(mr);
  Thread t3 = new Thread(mr);

  t1.start();
  t2.start();
  t3.start();
}
}

A saída será parecida com a seguinte:

Meu nome é: Thread-0
Meu nome é: Thread-2
Meu nome é: Thread-1


E se mandar executar outra vez?

Meu nome é: Thread-2
Meu nome é: Thread-0
Meu nome é: Thread-1


E em cada execução o resultado poderá ser diferente. Não podemos prever a ordem que eles serão executados. Não há garantia nenhuma. O mecanismo responsável por alocar os threads (agendador) é quem decide quem vai entrar em execução (ou sair, ou esperar). A única coisa que podemos ter certeza é que cada thread será iniciado e executado até a sua conclusão.
Vamos ver mais um exemplo pra fechar o post. Faça a seguinte mudança no método run:
public void run() {
    for(int x = 0; x /* sinal de menor aqui */ 100; x++){
System.out.println("Meu nome é: " + Thread.currentThread().getName() + " e x = " + x);
}

A saída que eu obtive em uma das execuções foi:
Meu nome é: Thread-0 e x = 0
Meu nome é: Thread-0 e x = 1
Meu nome é: Thread-0 e x = 2
Meu nome é: Thread-1 e x = 0
Meu nome é: Thread-1 e x = 1
Meu nome é: Thread-1 e x = 2
Meu nome é: Thread-1 e x = 3
Meu nome é: Thread-1 e x = 4
Meu nome é: Thread-1 e x = 5
Meu nome é: Thread-0 e x = 3
Meu nome é: Thread-0 e x = 4
Meu nome é: Thread-0 e x = 5
...
Veja que Thread-0 toma a CPU e conta até 2, então sai de cena. Entra Thread-1. Por algum motivo que não sabemos explicar, a CPU permitiu que Thread-1 contasse até 5. Então a "bola" volta pra Thread-0 que continua. E Thread-2? Até o momento ainda não recebeu sua parcela da CPU, coitado!
Então finalizo este post e aproveito para dizer que o conteúdo passado aqui é somente o básico, tem muito mais sobre threads do que você imagina. Se possível outro dia volto a falar mais sobre o assunto.

Bons estudos!

4 comentários:

  1. Interessante, já tinha lido no livro do Rui Rossi e o post complementou bastante, alias, só tive coragem de estudar o assunto porque vi aqui.

    ResponderExcluir
    Respostas
    1. Obrigado pelo comentário. Fico feliz por encorajar seus estudos.

      Excluir