programação concorrente - gerenciamento de threads - parte i
TRANSCRIPT
UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO CONCORRENTE – 2015.1
Fábio M. Pereira
Roteiro
• Criando e executando uma thread
• Recuperando e atribuindo informações de uma thread
• Interrompendo uma thread
• Controlando a interrupção de uma thread
• Adormecendo e retomando uma thread
• Aguardando pela finalização de uma thread
Criando e Executando Uma Thread
• Vamos aprender como criar e executar uma thread em uma aplicação Java
• Tal como acontece com todos os elementos da linguagem Java, threads são objetos
• Temos duas maneiras de criar uma thread em Java: – Estendendo a classe Thread e substituindo o método run()
– A construção de uma classe que implementa a interface Runnable e, em seguida, criar um objeto da classe Thread passando o objeto que implementa a interface Runnable como um parâmetro
• Vamos usar a segunda abordagem para criar um programa simples que cria e executa 10 threads
• Cada thread calcula e imprime a tabela de multiplicação de um número entre um e 10
Criando o Programa
1. Crie um novo projeto Java
2. Crie uma classe chamada Calculadora que implemente a interface Runnable public class Calculadora implements Runnable
{
3. Declare um atributo private int chamado numero e implemente o construtor da classe inicializando o seu valor private int numero;
public Calculadora(int numero) {
this.numero=numero;
}
Criando o Programa
4. Implemente o método run(), este método irá executar as instruções da thread que criamos, assim, este método irá calcular a tabela de multiplicação para o número @Override
public void run() {
for (int i=1; i<=10; i++){
System.out.printf("%s: %d * %d = %d\n",
Thread.currentThread().getName(),
numero,i,i*numero);
}
}
5. Agora implemente a classe principal da aplicação, criando uma classe Main que contém o método main() public class Main {
public static void main(String[] args) {
Criando o Programa
6. Dentro do método main() crie um laço for para 10 iterações, dentro do laço crie um objeto da classe Calculadora, um objeto da classe Thread, passe o objeto Calculadora como parâmetro, e chame o método start() do objeto thread for (int i=1; i<=10; i++){
Calculadora calc=new Calculadora(i);
Thread thread=new Thread(calc);
thread.start();
}
Funcionamento...
• Execute o programa e veja como as diferentes threads trabalham em paralelo
Funcionamento...
• Cada programa Java tem pelo menos uma thread de execução
• Quando executamos o programa, a JVM executa essa thread de execução que chama o método main() do programa
• Quando chamamos o método start() de um objeto Thread, estamos criando uma outra thread de execução
• Nosso programa terá tantas threads de execução quanto chamadas do método start()
• Um programa Java termina quando todas as suas threads terminarem (mais especificamente, quando todas as suas threads não-daemon terminarem)
Funcionamento...
• Se a thread inicial (aquele que executa o método main()) termina, o restante das threads irá continuar com a sua execução até que terminem
• Se uma das threads chamar a instrução System.Exit() para terminar a execução do programa, todas as threads irão terminar a sua execução
• Criar um objeto da classe Thread não cria uma nova thread de execução
• Além disso, chamar o método run() de uma classe que implementa a interface Runnable não cria uma nova thread de execução
• Apenas chamar o método start() cria uma nova thread de execução
Funcionamento...
• Como mencionado no início, há uma outra maneira de criar uma nova thread de execução:
– Podemos implementar uma classe que estende a classe Thread
e substituir o método run() da classe
– Em seguida, podemos criar um objeto dessa classe e chamar o método start() para ter uma nova thread de execução
Recuperando e Atribuindo Informações de Uma Thread
• A classe Thread salva alguns atributos de informações que podem nos ajudar a identificar uma thread, saber o seu status, ou controlar a sua prioridade
• Estes atributos são: – ID: armazena um identificador único para cada Thread
– Nome: armazena o nome da Thread
– Prioridade: armazena a prioridade dos objetos Thread. Threads podem ter uma prioridade entre um e dez, onde um é a prioridade mais baixa e dez é a mais alta. Não é recomendado alterar a prioridade das threads, mas é uma possibilidade que podemos usar se quisermos
– Status: armazena o status da Thread. Em Java, Thread pode estar em um desses seis estados: new, runnable, blocked, waiting, time waiting, ou terminated
Recuperando e Atribuindo Informações de Uma Thread
• Neste exemplo, vamos desenvolver um programa que estabelece o nome e prioridade para 10 threads e, em seguida, mostra informações sobre o seu estado até que elas terminem
• As threads irão calcular a tabela de multiplicação de um número
Criando o Programa
1. Repita os passos de 1 a 5 do exemplo anterior para criação da classe Calculadora e do programa principal
6. Agora crie um array de 10 elementos Thread e um array com 10 elementos Thread.State para armazenar as threads que iremos executar e os seus status Thread threads[]=new Thread[10];
Thread.State status[]=new Thread.State[10];
Criando o Programa
7. Crie 10 objetos da classe Calculadora, cada um inicializado com um número diferente, e 10 threads para executá-los, atribua valor máximo de prioridade a cinco delas e valor de prioridade mínimo às outras cinco for (int i=0; i<10; i++){
threads[i]=new Thread(new Calculadora(i+1));
if ((i%2)==0){
threads[i].
setPriority(Thread.MAX_PRIORITY);
} else {
threads[i].
setPriority(Thread.MIN_PRIORITY);
}
threads[i].setName("Thread "+i);
}
Criando o Programa
8. Crie um objeto PrintWriter para escrever em um arquivo a evolução do status das threads try (
FileWriter file = new FileWriter( ".\\log.txt"); // "./log.txt" no Linux!
PrintWriter pw = new PrintWriter(file);) {
// O resto do programa (itens 9 a 11) vai aqui!
// ...
} catch (IOException e) { e.printStackTrace(); }
9. Escreva neste arquivo o status das 10 threads for (int i=0; i<10; i++){
pw.println("Main : Status da Thread "+i+" : “
+ threads[i].getState());
status[i]=threads[i].getState();
}
10. Inicie a execução das 10 threads for (int i=0; i<10; i++){
threads[i].start();
}
Criando o Programa
11. Até que as 10 threads terminem, iremos checar os seus status, se detectarmos uma mudança no status da thread, iremos escrevê-la no arquivo boolean finish=false;
while (!finish) {
for (int i=0; i<10; i++){
if (threads[i].getState()!=status[i]) {
writeThreadInfo(pw, threads[i],status[i]);
status[i]=threads[i].getState();
}
}
finish=true;
for (int i=0; i<10; i++){
finish=finish &&
(threads[i].getState()==
Thread.State.TERMINATED);
}
}
pw.close();
Criando o Programa
12. Implemente o método writeThreadInfo() que escreve a ID, nome, prioridade, status anterior e novo status da thread private static void writeThreadInfo(PrintWriter
pw, Thread thread, Thread.State state) {
pw.printf("Main : Id %d - %s\n",thread.getId(),
thread.getName());
pw.printf("Main : Prioridade: %d\n",
thread.getPriority());
pw.printf("Main : Estado anterior: %s\n",
state);
pw.printf("Main : Novo Estado: %s\n",
thread.getState());
pw.printf("Main : **********************" + "**************\n");
}
Criando o Programa
• Execute o exemplo e abra o arquivo log.txt para ver a evolução das 10 threads
Funcionamento...
• O programa mostra no console as tabuadas calculadas pelas threads e a evolução do estado das diferentes threads no arquivo log.txt, desta forma podemos visualizar melhor a evolução das threads
• A classe Thread tem atributos para armazenar todas as informações de uma thread
• A JVM utiliza a prioridade das threads para selecionar aquela que usa a CPU em cada momento e atualiza o status de cada thread de acordo com a sua situação
• Se não especificarmos um nome para uma thread, a JVM automaticamente atribui a ela um nome com o formato, Thread-XX, onde XX é um número
• Não podemos modificar a ID ou o status de uma thread, a classe Thread não implementa os métodos setId() e setStatus() para permitir a sua alteração
Funcionamento...
• Também podemos acessar os atributos de uma thread a partir da implementação da interface Runnable, podemos usar o método estático CurrentThread() da classe Thread para acessar o objeto Thread que está executando o objeto Runnable
• Devemos levar em conta que o método setPriority() pode lançar uma exceção IllegalArgumentException se tentarmos estabelecer uma prioridade que não está entre 1 e 10
Interrompendo Uma Thread
• Um programa Java com mais de uma thread de execução só termina quando a execução de todas as suas threads e, mais especificamente, quando todas as suas threads não-daemon terminam a sua execução ou quando uma das threads usam o método System.exit()
• Às vezes, vamos precisar terminar uma thread, porque queremos encerrar um programa, ou quando um usuário do programa quer cancelar as tarefas que um objeto Thread está fazendo
• Java fornece um mecanismo de interrupção para indicar a uma thread que queremos terminá-la
Interrompendo Uma Thread
• Uma peculiaridade deste mecanismo é que a Thread
tem que verificar se ela foi interrompida ou não, e pode decidir se ela responde ao pedido de finalização ou não
• A Thread pode ignorá-lo e continuar com a sua execução
Programa Exemplo
1. Crie uma classe chamada PrimeGenerator que estende a classe Thread
public class PrimeGenerator extends Thread{
2. Sobrescreva o método run() incluindo um laço que irá rodar indefinidamente. Neste laço iremos processar números consecutivos iniciando por um. Para cada número iremos calcular se ele é primo e, neste caso, iremos imprimi-lo no console. @Override
public void run() {
long number=1L;
while (true) {
if (isPrime(number)) {
System.out.printf("O numero %d eh Primo\n",
number);
}
...
Programa Exemplo
3. Após processar um número, verifique se a thread foi interrompida através do método isInterrupted(). Se o método retornar true, escreveremos uma mensagem e terminaremos a execução da thread. if (isInterrupted()) {
System.out.printf("O Gerador de
Primos foi interrompido!\n");
return;
}
number++;
}
}
Programa Exemplo
4. Implemente o método isPrime(). Ele irá retornar um valor boolean indicando se o número recebido como parâmetro é um número primo (true) ou não (false). private boolean isPrime(long number) {
if (number <=2) {
return true;
}
for (long i=2; i<number; i++){
if ((number % i)==0) {
return false;
}
}
return true;
}
Programa Exemplo
5. Agora implemente a classe principal do exemplo (Main), implementando o método main(). public class Main {
public static void main(String[] args) {
6. Crie e inicie um objeto da classe PrimeGenerator. Thread task=new PrimeGenerator();
task.start();
7. Espere por 5 segundos e interrompa a thread PrimeGenerator. try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
task.interrupt();
8. Rode o programa e veja os resultados.
Funcionamento...
Funcionamento...
• A classe Thread tem um atributo que armazena um valor booleano que indica se a thread foi interrompida ou não
• Quando chamamos o método interrupt() de uma thread, definimos esse atributo para true
• O método isInterrupted() retorna apenas o valor do atributo
• A classe Thread tem outro método para verificar se foi interrompida ou não, é o método estático, interrupted(), que verifica se a thread atual em execução foi interrompida ou não – O método interrupted(), diferentemente de isInterrupted(),
modifica o valor do atributo interrupted para false
– Por se tratar de um método estático, a sua utilização não é recomendada
Controlando a Interrupção de Uma Thread
• O mecanismo mostrado no exemplo anterior, pode ser utilizado se a thread que pode ser interrompida é simples, mas se a thread implementa um algoritmo complexo dividido em alguns métodos, ou se tem métodos com chamadas recursivas, podemos usar um mecanismo melhor para controlar a interrupção da thread
• Java fornece a exceção InterruptedException com esta finalidade, podemos lançar essa exceção quando detectarmos a interrupção da thread e capturá-la no método run()
Controlando a Interrupção de Uma Thread
• Neste exemplo, vamos implementar uma Thread que procura arquivos com um determinado nome em uma pasta e em todas as suas subpastas para mostrar como usar a exceção InterruptedException para controlar a interrupção de uma thread
Programa Exemplo
1. Crie uma classe chamada FileSearch e especifique que ela implementa a interface Runnable. public class FileSearch implements Runnable {
2. Declare dois atributos privados, um para o nome do arquivo que será procurado e outro para a pasta inicial. Implemente o construtor dessa classe, que inicializa esses atributos. private String initPath;
private String fileName;
public FileSearch(String initPath, String fileName) {
this.initPath = initPath;
this.fileName = fileName;
}
Programa Exemplo
3. Implemente o método run() da classe FileSearch. Ele verifica se o atributo initPath é uma pasta e, se for, chama o método processDirectory(). Este método pode lançar uma exceção InterruptedException, devendo capturá-la. @Override
public void run() {
File file = new File(initPath);
if (file.isDirectory()) {
try {
directoryProcess(file);
} catch (InterruptedException e) {
System.out.printf("%s: A busca foi interrompida",
Thread.currentThread().getName());
}
}
}
Programa Exemplo
4. Implemente o método directoryProcess(). Este método irá obter e processar os arquivos e subpastas da pasta. Para cada subpasta, o método irá fazer uma chamada recursiva passando a pasta como parâmetro. Para cada arquivo, o método irá chamar o métodos fileProcess(). Após processar todos os arquivos e pastas, o método verifica se a Thread foi interrompida e, neste caso, lança uma exceção InterruptedException.
Programa Exemplo
private void directoryProcess(File file) throws
InterruptedException {
System.out.printf("Processando %s ...\n",
file.getName());
File list[] = file.listFiles();
if (list != null) {
for (int i = 0; i < list.length; i++) {
if (list[i].isDirectory()) {
directoryProcess(list[i]);
} else {
fileProcess(list[i]);
}
}
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
Programa Exemplo
5. Implemente o método processFile(). Este método irá comparar o nome do arquivo que está sendo processado com o nome que estamos buscando. Se forem iguais, iremos escrever uma mensagem no console. Após esta comparação, a Thread irá verificar se foi interrompida e, neste caso, irá lançar uma exceção InterruptedException. private void fileProcess(File file) throws InterruptedException {
if (file.getName().equals(fileName)) {
System.out.printf("%s : %s !!!\n", Thread.currentThread().getName(),
file.getAbsolutePath());
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
Programa Exemplo
6. Agora vamos implementar a classe principal do exemplo. Implemente a classe Main que contém o método main(). public class Main {
public static void main(String[] args) {
7. Crie e inicialize um objeto da classe FileSearch e uma Thread para executar a sua tarefa. Então inicie a execução da Thread. FileSearch searcher=new
FileSearch("C:\\","autoexec.bat");
// Modificar! Principalmente no Linux…
Thread thread=new Thread(searcher);
thread.start();
Programa Exemplo
8. Aguarde por 10 segundos e interrompa a Thread. try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
9. Execute o programa e veja os resultados.
Funcionamento...
Funcionamento...
• Neste exemplo, usamos exceções Java para controlar a interrupção da Thread
• Quando executamos o exemplo, o programa começa a passar por pastas, verificando se elas têm ou não o arquivo
• Por exemplo, se você entrar na pasta \b\c\d, o programa terá três chamadas recursivas ao método processDirectory()
• Quando se detecta que ele foi interrompido, ele lança uma exceção InterruptedException e continua a execução no método run(), não importa quantas chamadas recursivas foram feitas
• A exceção InterruptedException é lançada por alguns métodos Java relacionados com a API de concorrência, como em sleep()
Adormecendo e Retomando Uma Thread
• Às vezes, podemos estar interessado em interromper a execução da Thread durante um determinado período de tempo
– Por exemplo, uma thread em um programa verifica um estado de um sensor uma vez por minuto, o resto do tempo, a thread não faz nada
– Durante este tempo, a thread não utiliza recursos do computador
– Após este tempo, a thread estará pronta para continuar com a sua execução, quando a JVM escolhe-la para ser executada
• Podemos usar o método sleep() da classe Thread para este fim
• Este método recebe um número inteiro como parâmetro indicando o número de milissegundos que a thread suspende a sua execução
Adormecendo e Retomando Uma Thread
• Quando o tempo de “dormência” acaba, a thread continua com a sua execução, na instrução após a chamada do método sleep(), quando a JVM lhe atribui tempo de CPU
• Outra possibilidade é a utilização do método sleep() de um elemento de enumeração TimeUnit
• Este método utiliza o método sleep() da classe Thread
para colocar a thread atual para dormir, mas ele recebe o parâmetro na unidade que ele representa e o converte para milissegundos
• O exemplo a seguir utiliza o método sleep() para mostrar a data atual a cada segundo
Programa Exemplo
1. Crie uma classe chamada FileClock e especifique que ela implementa a interface Runnable. public class FileClock implements Runnable {
2. Implemente o método run(). @Override
public void run() {
Programa Exemplo
3. Faça um laço com 10 iterações. Em cada iteração, criar um objeto Date, escrevê-lo para o arquivo, e chamar o método sleep() do atributo SECONDS da classe TimeUnit para suspender a execução da thread por um segundo. Com este valor, a thread ficará adormecida por cerca de um segundo. Como o método sleep () pode lançar uma exceção InterruptedException, temos que incluir o código para capturá-la. É uma boa prática incluir código que libera ou fecha os recursos que a thread está usando quando ela é interrompida.
Programa Exemplo
for (int i = 0; i < 10; i++) {
System.out.printf("%s\n", new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.printf("FileClock foi
interrompido!");
}
}
}
Programa Exemplo
4. Crie uma classe chamada FileMain que contém o método main(). public class FileMain {
public static void main(String[] args) {
5. Crie um objeto da classe FileClock e a thread para executá-lo. Então, inicie a execução da Thread. FileClock clock=new FileClock();
Thread thread=new Thread(clock);
thread.start();
Programa Exemplo
6. Chame o método sleep() do atributo SECONDS da classe TimeUnit na Thread principal para esperar por 5 segundos. try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
};
7. Interrompa a thread FileClock. thread.interrupt();
8. Execute o exemplo e veja os resultados.
Funcionamento...
• Quando executamos o exemplo, podemos ver como o programa escreve um objeto Date por segundo e, em seguida, a mensagem indicando que a thread FileClock
foi interrompida é mostrada
Funcionamento...
• Quando chamamos o método sleep(), a Thread deixa a CPU e para a sua execução por um período de tempo
• Durante este tempo, ela não está consumindo tempo de CPU, de modo que o CPU pode executar outras tarefas
• Quando a Thread está dormindo e é interrompida, o método gera uma exceção InterruptedException imediatamente e não espera até que a hora de inatividade acabe
• A API de concorrência Java tem outro método que faz com que um objeto Thread deixe a CPU, é o método yield(), que indica à JVM que o objeto Thread pode liberar a CPU para outras tarefas, A JVM não garante que ele irá cumprir com este pedido
• Normalmente, ele é usado apenas para fins de depuração
Aguardando Pela Finalização de Uma Thread
• Em algumas situações, temos que esperar pela finalização de uma thread
• Por exemplo, podemos ter um programa que começará inicializando os recursos que necessita antes de prosseguir com o resto da execução
• Podemos executar as tarefas de inicialização como threads e aguardar sua finalização antes de continuar com o resto do programa
• Para isso, podemos usar o método join() da classe Thread
• Quando chamamos este método usando um objeto thread, ele suspende a execução da thread de chamada até que o objeto chamado termine a sua execução
Programa Exemplo
1. Crie uma classe chamada DataSourcesLoader e especifique que ela implementa a interface Runnable. public class DataSourcesLoader implements
Runnable {
2. Implemente o método run(). Ele escreve uma mensagem para indicar que iniciou a sua execução, espera por 4 segundos, e escreve uma outra mensagem para indicar que finalizou a sua execução.
Programa Exemplo
@Override
public void run() {
System.out.printf("Iniciando a carga de
recursos: %s\n",new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(“Carga de recursos
finalizada: %s\n",new Date());
}
Programa Exemplo
3. Crie uma classe chamada NetworkConnectionsLoader
e especifique que ela implementa a interface Runnable. Implemente o método run(). Será igual ao método de run() da classe DataSourceLoader, mas esta vai adormecer durante 6 segundos.
4. Agora crie uma classe chamada Main que contenha o método main(). public class Main {
public static void main(String[] args) {
Programa Exemplo
5. Crie um objeto da classe DataSourcesLoader e uma Thread para executá-lo. DataSourcesLoader dsLoader = new
DataSourcesLoader();
Thread thread1 = new
Thread(dsLoader,"DataSourceThread");
6. Crie um objeto da classe NetworkConnectionsLoader
e uma Thread para executá-lo. NetworkConnectionsLoader ncLoader = new
NetworkConnectionsLoader();
Thread thread2 = new
Thread(ncLoader,
"NetworkConnectionThread");
Programa Exemplo
7. Chame o método start() dos dois objetos Thread. thread1.start();
thread2.start();
8. Aguarde a finalização de ambos os segmentos usando o método join(). Este método pode lançar uma exceção InterruptedException, por isso temos de incluir o código para capturá-la. try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Programa Exemplo
9. Escreva uma mensagem para indicar o fim do programa. System.out.printf("Main: A configuracao foi
carregada: %s\n",new Date());
10. Execute o programa e veja os resultados.
Funcionamento...
• Quando executarmos este programa, podemos ver como os dois objetos de Thread iniciam a sua execução
• Em primeiro lugar, a thread DataSourcesLoader termina a sua execução
• Em seguida, a classe NetworkConnectionsLoader
termina a sua execução e, naquele momento, o objeto Thread principal continua sua execução e escreve a mensagem final
O Método join()
• Java fornece duas formas adicionais do método join():
– join (long milissegundos)
– join (long milissegundos, long nanos)
• Na primeira versão do método join(), em vez de esperar indefinidamente pela finalização da thread chamada, a thread que faz a chamada aguarda os milissegundos especificados como um parâmetro do método
• Por exemplo, se o objeto thread1 tem o código, thread2.join(1000), a thread thread1 suspende a sua execução, até que uma destas duas condições seja verdadeira: – thread2 termina a sua execução
– 1.000 milissegundos tenham passado
O Método join()
• A segunda versão do método join() é similar à primeira, mas recebe o número de milissegundos e o número de nano segundos como parâmetros
Referências
• González, J. F. Java 7 Concurrency Cookbook. Pack Publishing, 2012.
UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO CONCORRENTE – 2015.1
Fábio M. Pereira