O documento descreve conceitos de programação concorrente em Java, incluindo:
1) Como criar e executar threads para realizar tarefas concorrentes;
2) Como recuperar informações sobre o estado e prioridade de threads;
3) Como interromper uma thread durante sua execução.
Programação Concorrente - Gerenciamento de Threads - Parte I
1. UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA
CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO CONCORRENTE – 2015.1
Fábio M. Pereira
(fabio.mpereira@uesb.edu.br)
2. 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
3.
4. 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
5. 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;
}
6. 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 = %dn",
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) {
7. 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();
}
9. 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)
10. 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
11. 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
12.
13. 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
14. 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
15. 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];
16. 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);
}
17. 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();
}
18. 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();
19. 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 - %sn",thread.getId(),
thread.getName());
pw.printf("Main : Prioridade: %dn",
thread.getPriority());
pw.printf("Main : Estado anterior: %sn",
state);
pw.printf("Main : Novo Estado: %sn",
thread.getState());
pw.printf("Main : **********************" +
"**************n");
}
20. Criando o Programa
• Execute o exemplo e abra o arquivo log.txt para ver a
evolução das 10 threads
21. 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
22. 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
23.
24. 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
25. 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
26. 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 Primon",
number);
}
...
27. 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++;
}
}
28. 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;
}
29. 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.
31. 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
32.
33. 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()
34. 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
35. 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;
}
36. 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());
}
}
}
37. 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.
38. 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();
}
}
39. 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();
}
}
40. 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();
41. 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.
43. 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 bcd, 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()
44.
45. 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
46. 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
47. 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() {
48. 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.
49. Programa Exemplo
for (int i = 0; i < 10; i++) {
System.out.printf("%sn", new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.printf("FileClock foi
interrompido!");
}
}
}
50. 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();
51. 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.
52. 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
53. 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
54.
55. 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
56. 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.
57. Programa Exemplo
@Override
public void run() {
System.out.printf("Iniciando a carga de
recursos: %sn",new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(“Carga de recursos
finalizada: %sn",new Date());
}
58. 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) {
59. 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");
60. 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();
}
61. Programa Exemplo
9. Escreva uma mensagem para indicar o fim do programa.
System.out.printf("Main: A configuracao foi
carregada: %sn",new Date());
10. Execute o programa e veja os resultados.
62. 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
63. 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
64. 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
66. UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA
CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO CONCORRENTE – 2015.1
Fábio M. Pereira
(fabio.mpereira@uesb.edu.br)