Introdução

Uma thread (linha de execução) é uma sequência de instruções que podem ser executadas em um programa. Java proporciona que um programa use diversas linhas de execução. Ou seja, seu programa pode executar, ou fazer parecer que esteja realmente executando, duas ou várias operações diferentes num mesmo instante.

O processo de usar duas ou mais threads é denominado de mutithreading (multilinhas). Um exemplo famoso de software mutithreading é um browser. Ele permite o download dos arquivos de diversas páginas e a abertura de uma nova janela simultaneamente. Para proporcionar essas ações ao mesmo tempo, o browser usa multithreading.

Criando um thread

Um thread Java inicia com uma instância de java.lang.Thread. Toda ação inicia também no método run(). O método run() fará chamada a outros métodos, obviamente, mas o thread de execução e a nova pilha de chamadas começará chamando run(). É possivel definir um thread de uma das duas maneiras:

  • Implementar a interface Runnable
    
                    package devmedia;
                    public class Teste implements Runnable{
                    public void run(){
                    for(int x = 0; x < 50; x++)
                    System.out.println(“X: ” + x);
                    }
                    public static void main(String args[ ]){
                    Teste teste = new Teste();
                    Thread th = new Thread(teste);
                    th.start();
                    }
                    }
                    
  • Estender a classe java.lang.Thread
    
                    package devmedia;
                    public class Teste extends Thread{
                    public void run(){
                    for(int x = 0; x < 50; i++)
                    System.out.println(“X: ” + x);
                    }
                    public static void main(String args[ ]){
                    Teste teste = new Teste();
                    teste.start();
                    }
                    }
                    

Estender a classe Thread é mais simples, porém, normalmente, não é uma prática tão boa de Orientação a Objetos(OO). Isso é fato, pois a criação de subclasses deve ser reservada para classes que estendam uma classe que já existe, por elas serem uma versão mais especializada de uma superclasse mais genérica.

Então, há apenas uma situação em que faz sentido estender um Thread, é quando temos uma versão mais especializada de uma classe Thread. Ou seja, porque se tem um comportamento de Thread mais especializado.

Se for criado um thread utilizando o construtor sem argumentos, ele chamará seu próprio método run() quando for o momento de começar a trabalhar. É justamente essa a intenção quando se estende Thread, mas quando for usado Runnable, terá que ser informado ao novo thread que ele empregue seu método run(), ao invés do que ele tiver.

Note que esse é o conceito de computação multithreading. A própria classe Thread implementa Runnable, pois ela tem um método run() que estamos sobrescrevendo. Isso quer dizer que você poderia passar um Thread a um outro construtor de Thread. Veja o exemplo.


        package devmedia;
        class MyThread extends Thread{
        public void run(){

        for(int x = 0; x < 50; x++)
        System.out.println(“blablabla”);
        }
        public static void main(String args[ ]){
        MyThread my = new MyThread();
        my.start();
        }
        }
        class Exemplo implements Runnable{
        public void run(){
        for(int x = 0; x < 50; x++)
        System.out.println(“blablabla”);
        }
        public static void main(String args[ ]){
        Exemplo ex = new Exemplo();
        Thread th1 = new Thread(ex);
        Thread th2 = new Thread(new MyThread());
        }
        }
        

Existem mais alguns construtores sobrecarregados da classe Thread. Os construtores importantes para nós são:

  • Thread()
  • Thread(String nome)
  • Thread(Runnable destino)
  • Thread(Runnable destino, String nome)

Quando um thread tiver sido instanciado, mas não iniciado, em outras palavras, o método start() ainda não foi invocado por uma instância de Thread, falamos que ela está no estado novo. Nessa fase, o thread ainda não é considerado ativo. Apenas após o método start() ter sido chamado é que o thread pode ser considerado como ativo, embora o método run() possa ainda não ter iniciado sua execução.

Um thread é considerado como desativado, não mais ativo, após o método run() ser concluído. O método isAlive() é a melhor opção de verificar se um thread foi iniciado, mas não finalizou ainda seu método run().

Antes de start() ser chamado em uma instância de Thread, se diz que o thread de execução está no estado novo. Invoque start() em uma instância de Thread() e não de Runnable. Não existe nada de especial no método run(). Assim como main(), ele é basicamente o nome do método que o novo thread chamará.

Entretanto, isso não quer dizer que o método run() será executado em um thread a parte (separado). Chamar run() diretamente significa somente que você está chamando um método independentemente do thread que está sendo executado e ele entrará na pilha de chamadas atual, em vez de no começo de uma nova pilha.

Para obtermos o nome de um thread, chamamos getName() na instância do thread. Porém a instância Runnable de destino não tem uma referência à instância de Thread, então primeiro chamamos o método static Thread.currentThread(), que retorna uma referência ao thread que estiver sendo executado e, logo em seguida, chamamos getName() na referência que foi retornada.

Existem muitas questões no exame pertinentes à previsibilidade dos threads. Muito atenção, a maioria dos comportamentos não é garantida.

Um thread deixa de ser um thread quando seu método run() é finalizado.Ou seja, quando um thread conclui seu método run(), deixa de ser um thread de execução. A pilha desse thread se dissolve e ele é considerado como inativo. Uma vez que o thread esteja inativo, nunca mais poderá ser reiniciado. Se houver uma referência a um Thread e invocarmos start() ele é iniciado. Se chamar start() novamente, isso causará uma exceção IllegalThreadStateException, que é um RunTimeException.

É o agendador de threads que escolhe qual thread, de todos os qualificados, será processado. A ordem na qual os threads executáveis são selecionados não é garantida. Mesmo que o comportamento da fila seja comum, ele não é garantido.

Na realidade, chamamos isso de pool executável, em vez de fila executável, para facilitar e reforçar o fato de que os threads não ficam todos alinhados numa ordem correta.

Quando o thread está no estado executável ele é considerado ativo. Isso quer dizer que uma invocação ao método isAlive() tem como retorno true.

Existem diversas maneiras de chegar ao estado executável, mas apenas uma de entrar no estado de execução: o agendador selecionar um thread no pool executável.

Espera/Suspensão/Bloqueio é o estado de um thread quando está apto para executar. Sendo assim, temos três estados em um; mas todos possuem uma coisa em comum: o thread ainda está ativo, porém não está qualificado para execução. Um thread pode ficar suspenso porque seu código de execução o informa para ficar inativo por algum período, situação em que acontecerá o retorno ao estado executável devido ao período de tempo ter expirado.

Concluindo, um thread é considerado inativo quando seu método run() é finalizado.

Suspensão

O método sleep() é um método estático da classe Thread. Utilize-o em seu código para diminuir a velocidade de um thread, forçando-o a entrar no modo de suspensão antes dele retornar ao estado executável, no qual ainda terá que esperar para ser o thread executado.

Pode vir-lhe a pergunta: por que eu iria querer um thread suspenso? Por achar que o thread está percorrendo um código de forma muito rápida. Ou forçar seus threads a dar oportunidades a outros, já que uma execução adequadamente agendada não é garantida nas especificações Java.

Veja um exemplo.


        package devmedia;
        public class X implements Runnable{
        public void run(){
        for(int x = 0; x < 100; x++)
        System.out.println(Thread.currentThread().getName());
        try{
        Thread.sleep(4000);
        }
        catch(InterruptedException e)
        {
        e.printStackTrace();
        }
        }
        public static void main(String args[ ]){
        X x = new X();
        Thread th1 = new Thread(x);
        Thread th2 = new Thread(x);
        Thread th3 = new Thread(x);
        th1.start();
        th2.start();
        th3.start();
        }
        }
        

Saída:
Thread-1
Thread-0
Thread-2

Utilizar Thread.sleep() é a melhor opção de fazer com que todos os threads tenham a chance de ser executados. O tempo especificado em sleep() será o período mínimo durante o qual o thread não será executado, porém não será o período completo que um thread não executará. De modo que não se pode, por exemplo, confiar no método sleep() como um timer 100% preciso.

Na parte seguinte desse artigo continuaremos falando de threade partiremos de prioridades.


Leia todos artigos da série