threads em java - Inf

Propaganda
THREADS EM JAVA
Gabriel de Oliveira Ramos <[email protected]>
Roland Teodorowitsch <[email protected]> - Orientador
Universidade Luterana do Brasil (Ulbra) – Curso de Ciência da Computação – Campus Gravataí
Av. Itacolomi, 3.600 – Bairro São Vicente – CEP 94170-240 – Gravataí - RS
15 de novembro de 2009
RESUMO
Este artigo apresenta o uso de threads na linguagem de programação Java, descrevendo a classe Thread bem
como a interface Runnable. Menciona ainda algumas considerações importantes, como o ciclo de vida de threads,
bem como questões sobre prioridades e sincronização de threads. Ao final, mostra um exemplo de aplicativo que
utiliza threads bem como uma análise de desempenho de sua execução com e sem threads.
Palavras-chave: Thread; Java.
ABSTRACT
Title: “Java Threads”
This paper presents the use of threads in Java programming language, describing the Thread class and the
Runnable interface. It mentions too some important considerations about the threads lifecycle and something about
threads priorities and synchronization. Finally, shows a thread application example and an analysis of its execution.
Keywords: Thread; Java.
1
INTRODUÇÃO
O desempenho computacional é um assunto amplamente discutido desde os primórdios da
computação. Atualmente, a maioria dos sistemas operacionais trabalha com multiprocessamento para
conferir maior desempenho na execução de processos.
Em um sistema operacional multiprocessado, algoritmos de escalonamento são utilizados para
dividir a utilização da CPU entre os processos em execução. Este mecanismo confere uma resposta muito
mais ágil para o usuário uma vez que as tarefas parecem ser executadas ao mesmo tempo. Entretanto, este
mecanismo não torna a execução de duas etapas distintas de um processo paralelas, torna paralela apenas a
execução dos processos entre si. É neste ponto que se torna necessário o uso de threads.
Threads podem ser definidas como fluxos seqüencias de execução de um programa. Logo, um
programa pode dividir partes seqüenciais não concorrentes de sua execução em threads distintas, conferindo
paralelismo na execução destas. Claro, partes concorrentes podem ser separadas em threads também,
entretanto este tipo de procedimento não confere paralelismo, não justificando o uso desta técnica.
Este artigo fala sobre o uso de threads na linguagem Java. Neste contexto, este artigo descreve a
classe Thread e a interface Runnable, que podem ser utilizadas para se trabalhar com threads em Java. Na
seqüencia é abordado o ciclo de vida de uma thread na máquina virtual Java (JVM), prioridades e
sincronização de threads.
Está presente neste artigo, também, um exemplo de aplicativo que utiliza threads, bem como uma
análise do resultado de suas execuções utilizando quantidades variadas de threads.
2
THREADS EM JAVA
A máquina virtual Java permite que uma aplicação tenha diversos fluxos seqüências de execução
rodando concorrentemente (SUN, 2009a). Para se utilizar esta funcionalidade, a API Java disponibiliza a
classe Thread e a interface Runnable.
2.1
A classe Thread
A classe Thread é uma classe nativa da linguagem Java, existente desde a API 6.1, que “permite
representar um fluxo independente de execução dentro de um programa” (JANDL, 2007, p. 262). Pode-se
1
definir uma thread a partir da criação de uma classe filha de Thread, ou seja, uma subclasse. Uma subclasse
de Thread exige a implementação do método run(), que contém o código a ser executado pela thread em si.
A Figura 1 mostra um exemplo de código de uma classe filha de Thread que cria aleatoriamente números de
0 a 99, parando apenas ao encontrar o número 50.
public class ExemploThread extends Thread {
public void run() {
int total = 0;
while ((int)(Math.random()*100) != 50)
total++;
System.out.println("Sou a " + this.getName() + " e tentei " + total + " vezes.");
}
}
Figura 1 – Exemplo de subclasse de Thread
Para criar uma thread propriamente dita a partir de uma subclasse de Thread, basta declarar um
objeto desta, utilizando o operador new para instanciá-la, bem como em qualquer outro tipo de objeto em
Java. Instanciado o objeto a thread está pronta para ser rodada. Para isto, deve-se chamar o método start(),
responsável por providenciar o seu escalonamento pelo sistema operacional e por executar o método run()
(JANDL, 2007, p. 263). A Figura 2 mostra um exemplo deste processo de criação da thread.
ExemploThread t = new ExemploThread();
t.start();
Figura 2 – Exemplo de instanciação de um objeto de Thread e sua conseqüente execução
Ao executar este código mais de uma vez, o resultado obtido pode ser o mesmo ou não, “refletindo
mais o estado do sistema do que da aplicação em si” (JANDL, 2007, p.264).
A JVM mantém um programa em execução enquanto suas threads permanecem ativas. De acordo
com a Sun (2009a), uma thread é finalizada quando chega ao fim do método run() ou quando gera uma
exceção não tratada, fazendo com que esta última seja lançada para o nível superior.
2.2
A interface Runnable
Segundo a Sun (2009b), “a linguagem de programação Java não permite herança múltipla”, no
entanto há uma alternativa para o uso de mais de uma superclasse, que é a utilização de interfaces. Com base
neste conceito, desde a API 6.2 existe a interface Runnable, que possui um funcionamento bem semelhante
ao da classe Thread, ao exigir a implementação do método run().
Diferente de implementações que utilizam a classe Thread diretamente, as que utilizam a interface
Runnable possuem algumas particularidades. Após instanciar o objeto que possui a interface Runnable, é
necessário instanciar também um objeto da classe Thread que receba em seu método construtor uma
referência ao objeto da interface Runnable, bem como um nome para a thread criada. Após instanciar este
objeto da classe Thread, pode-se iniciar a thread a partir do seu método start(). A Figura 3 mostra um
exemplo de uma classe que implementa a interface Runnable, bem como da chamada desta classe a partir da
criação de um objeto de Thread.
public class ExemploRunnable implements Runnable {
public void run() {
int total = 0;
while ((int)(Math.random()*100) != 50)
total++;
}
}
public class Main {
public static void main(String[] args) {
ExemploRunnable r = new ExemploRunnable();
Thread t = new Thread(r, "Thread 1");
t.start();
}
}
Figura 3 – Exemplo de utilização de uma interface Runnable
2
Esta interface torna-se muito importante, pois em diversos momentos é necessário que uma classe
tenha dois pais ao mesmo tempo. Um bom exemplo deste tipo de aplicação é para interfaces gráficas. Uma
classe criada para fazer uma janela, por exemplo, pode ser filha da classe JPanel e, para possibilitar
paralelismo e uma melhor experiência para o usuário, pode implementar a classe Runnable.
2.3
Ciclo de vida de uma Thread
Uma thread, do momento em que é criada até o qual é finalizada, passa por diversos estados e
transições. Este conjunto de estados e transições caracteriza o ciclo de vida de uma thread, o qual pode ser
visualizado na Figura 4.
Figura 4 – Ciclo de vida de threads
Como mencionado anteriormente, as threads são criadas a partir de sua instanciação. Ao disparar o
método start(), ela será colocada na lista de threads prontas a serem executadas pelo sistema operacional.
Quando o sistema operacional seleciona uma thread para ser executada, ela permanece no estado Execução,
produzindo as ações do método run().
Segundo Jandl (2007), uma thread permanece em Execução até que: seu tempo de uso da CPU se
esgote ou com a chamada do método yield() quando executa um sleep(), permanecendo no estado Suspenso
durante o tempo estabelecido e voltando ao estado Pronto, em seguida; quando executa um wait(),
permanecendo no estado Suspenso até que outra thread execute um notify() ou notifyAll(), voltando ao
estado Pronto em seguida; quando necessita de uma operação de I/O, ficando no estado Bloqueado até que a
operação seja concluída, voltado ao estado Pronto; se a thread finalizar a execução do seu método run() ou
quando for executado o método interrupt(), indo para o estado Finalizado.
2.4
Prioridades
A prioridade de uma thread corresponde a preferência que ela terá perante as demais durante sua
execução. Quanto maior a prioridade de uma thread, maior será sua preferência no uso da CPU. Threads de
mesma prioridade costumam partilhar o tempo de CPU igualmente.
A prioridade é extremamente ligada ao algoritmo de escalonamento de CPU que o sistema
operacional utiliza. Para definir a prioridade de uma thread, são usados números de 1 a 10, sendo que o
número 5 é usado para definir a prioridade como normal. Entretanto, nem todos os sistemas operacionais
possuem as mesmas prioridades de uma thread Java. Portanto, para garantir que uma thread com prioridade
10 tenha prioridade alta tanto em um sistema operacional cuja prioridade máxima seja 10 quanto em outro
que seja 100, a JVM traduz a prioridade especificada no código para a do sistema operacional, logo uma
prioridade 10 em Java pode ser traduzida para uma prioridade 100, por exemplo.
3
2.5
Sincronização
Durante a execução de threads, há casos em que elas trabalham independentemente uma da outra,
sem necessidade de qualquer comunicação entre elas e há casos em que elas comunicam-se de alguma forma
ou utilizam dados em comum, denominadas assíncronas e síncronas, respectivamente.
Em threads síncronas, geralmente é necessário que a informação que está sendo compartilhada seja
consistente, evitando que duas threads mexam nesta ao mesmo tempo, gerando um resultado inconsistente.
Para tanto, na linguagem Java há um mecanismo de sincronização, onde esta consistência pode ser garantida.
Tal mecanismo pode ser utilizado com a palavra reservada synchronized, aplicada a um método, a um bloco
ou mesmo a uma diretiva isolada.
Os métodos wait(), notify() e notifyAll() também são muito importantes na sincronização, sendo
responsáveis por provocar, respectivamente: uma espera, a liberação de uma ou mais threads em espera
(JANDAL, 2007, p. 282).
3
EXEMPLO PRÁTICO
Para demonstrar o funcionamento de threads foi feito um aplicativo em Java para simular a
utilização da distribuição de uma tarefa entre n elementos processadores, conhecidos na área de sistemas
distribuídos como Eps (Elementos Processadores). A tarefa é básica: verificar se os números de um dado
intervalo são primos ou não.
3.1
O aplicativo
Neste aplicativo foram utilizadas duas classes: a classe EP, filha da classe Thread, que é responsável
por realizar a tarefa; e a classe Main, que instancia os objetos da classe EP executando-os em seguida.
Inicialmente é definido na classe Main o número de threads a ser utilizado pelo aplicativo e o
intervalo da seqüência de números a ser processada. Estes valores são armazenados em variáveis para que
sejam utilizados posteriormente pela classe EP. Em seguida, as instâncias da classe EP são criadas,
recebendo como parâmetros para seu método construtor: o número da thread, iniciando em 0; o número total
de threads, definido anteriormente; o início e o fim do intervalo, também definidos anteriormente. Após
instanciadas, as threads têm sua execução iniciada, para que a tarefa comece a ser resolvida.
A classe EP utiliza alguns conceitos básicos de computação distribuída. Portanto, foi necessário
dividir a seqüência de números entre as threads existentes. Para tal finalidade, uma thread verifica todos os
números da seqüência que são múltiplos de seu número, recebido como parâmetro no seu método construtor.
A Figura 5 mostra o código-fonte das duas classes mencionadas.
public class Main {
public static void main(String[] args) {
//Variável para determinar o número de threads
int nEP = 2;
//Variáveis para determinar o intervalo a ser processado
int iniS = 2, fimS = 1000;
//Instanciação das threads
EP ep0 = new EP(0, nEP, iniS, fimS);
EP ep1 = new EP(1, nEP, iniS, fimS);
//Início da execução das threads
ep0.start();
ep1.start();
}
}
public class EP extends Thread {
//Variáveis para guardar o número da thread atual
//e o número total de threads, respectivamente
private int EPid, nEP;
4
//Variáveis para determinar o intervalo a ser processado
private int iniS, fimS;
//Variável para controlar se o número é primo ou não
private boolean primo;
public EP(int EPid, int nEP, int iniS, int fimS) {
this.EPid = EPid;
this.nEP = nEP;
this.iniS = iniS;
this.fimS = fimS;
}
public void run() {
//Percorre apenas os númeroes correspondentes à thread,
//do intervalo especificado
for (int x = EPid + iniS; x <= fimS; x = x + nEP) {
//Limpa a variável de controle
primo = true;
//Verifica se o número é primo
for (int y = 2; y < x; y++)
if (x % y == 0)
primo = false;
//Imprime o resultado
if (primo)
System.out.println("O número " + x + " é primo.");
else
System.out.println("O número " + x + " não é primo.");
}
}
}
Figura 5 – Exemplo de um aplicativo utilizando threads
Como pode ser observado na Figura 5, neste caso estão sendo utilizadas apenas duas threads para
processar o intervalo de 2 a 1000.
3.2
Os resultados
Para demonstrar o desempenho do uso de threads em um aplicativo o código da Figura 5 foi
executado utilizando quantidades variadas de threads, com os tempos execução contabilizados, para o
intervalo de 2 a 100000 (entre dois e cem mil). Para tal tarefa foi utilizado um computador com processador
Intel Core2 Duo que, por possuir dois núcleos físicos, favorece a utilização de threads. O sistema
operacional utilizado foi o Windows Vista. A Tabela 1 apresenta os resultados obtidos nas diversas
execuções do aplicativo.
Tabela 1 – Relação dos tempos de execução, em segundos, do aplicativo
Threads utilizadas Tempo para 50000 números
Tempo para 100000 números
1
22
76
2
16
54
4
15
54
8
15
49
16
14
50
Como pode ser observado na Tabela 1, a utilização de mais de uma thread para executar uma tarefa
traz grandes ganhos. Para um intervalo de cinqüenta mil números, tem-se um ganho de aproximadamente
27% utilizando duas threads e de aproximadamente 32% utilizando oito threads. Já para um intervalo de
5
cem mil números o ganho sobe para quase 29% quando se utilizam duas threads e para quase 36% utilizando
oito threads. Percebe-se, portanto, que há um ganho considerável ao utilizar threads em um aplicativo.
4
CONCLUSÃO
Este artigo apresentou o uso de threads na linguagem de programação Java, bem como algumas de
suas principais características e propriedades, esclarecendo de uma forma simplificada seus conceitos
principais. Foi feita também uma análise prática dos benefícios que a utilização de threads em um aplicativo
pode trazer. Maiores detalhes e esclarecimentos mais aprofundados sobre o tema podem ser consultados nas
referências deste artigo.
REFERÊNCIAS
JANDL JUNIOR, Peter. Java: guia do programador. São Paulo: Novatec, 2007. 688 p.
SUN Microsystems. Thread (Java Platform SE 7 b66). Santa Clara: SUN, 2009. Disponível em:
<http://java.sun.com/javase/7/docs/api/java/lang/Thread.html>. Acesso em: 17 out. 2009.
SUN Microsystems. Interfaces. Santa Clara: SUN, 2009. Disponível em:
<http://java.sun.com/docs/books/tutorial/java/IandI/createinterface.html>. Acesso em: 18 out. 2009.
6
Download