Upload
fabio-moura-pereira
View
380
Download
0
Embed Size (px)
Citation preview
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 daemon thread
• Processando exceções em uma thread
• Utilizando variáveis de tread locais
• Agrupando threads
• Processando exceções em um grupo de threads
• Criando threads através de uma fábrica
Criando e Executando Uma Daemon Thread
• Java tem um tipo especial de thread chamada de daemon thread
• Este tipo de thread tem prioridade muito baixa e normalmente só é executada quando nenhuma outra thread do mesmo programa está sendo executada
• Quando daemon threads são as únicas threads em execução em um programa, a JVM termina o programa terminando estas threads
• Com essas características, as daemon threads são normalmente utilizadas como provedores de serviços para threads normais (também chamadas de usuários) em execução no mesmo programa
Criando e Executando Uma Daemon Thread
• Elas geralmente têm um laço infinito que aguarda a solicitação de serviço ou executa as tarefas da thread
• Elas não podem fazer tarefas importantes porque não sabemos quando elas vão ter tempo de CPU e podem terminar a qualquer momento se não existem quaisquer outras threads em execução
• Um exemplo típico deste tipo de thread é o coletor de lixo Java
• No exemplo utilizaremos duas threads: uma thread do usuário que grava eventos em uma fila e uma daemon que limpa essa fila, removendo os eventos que foram gerados mais de 10 segundos atrás
Programa Exemplo
1. Crie a classe Event, esta classe só armazena informações sobre os eventos que o nosso programa irá trabalhar. Declare dois atributos particulares, um chamado date do tipo java.util.Date e outro chamado de event do tipo String. Gerar os métodos para escrever e ler seus valores.
2. Crie uma classe WriterTask e especifique que ela implementa a interface Runnable. public class WriterTask implements Runnable {
3. Declare a fila que armazena os eventos e implemente o construtor da classe, que inicializa esta fila. private Deque<Event> deque;
public WriterTask (Deque<Event> deque){
this.deque=deque;
}
Programa Exemplo
4. Implemente o método run() para esta tarefa. Este método deverá ter um laço com 100 iterações. Cada iteração deverá criar um novo evento (Event), salvá-lo na fila e dormir por um segundo. @Override
public void run() {
for (int i=1; i<100; i++) {
Event event=new Event();
event.setDate(new Date());
event.setEvent(String.format("A thread %s gerou
um evento",Thread.currentThread().getId()));
deque.addFirst(event);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Programa Exemplo
5. Crie a classe CleanerTask e especifique que ela estende a classe Thread. public class CleanerTask extends Thread {
6. Declare a fila que armazena os eventos e implemente o construtor da classe, que inicializa esta fila. No construtor, marque esta thread como um daemon através do método setDaemon(). private Deque<Event> deque;
public CleanerTask(Deque<Event> deque) {
this.deque = deque;
setDaemon(true);
}
Programa Exemplo
7. Implemente o método run(). Este método possui um laço infinito que pega a data atual e chama o método clean(). @Override
public void run() {
while (true) {
Date date = new Date();
clean(date);
}
}
Programa Exemplo
8. Implemente o método clean(). Ele pega o último evento e, se foi criado mais de 10 segundos atrás, o apaga e verifica o próximo evento. Se o evento é apagado, escreve uma mensagem do evento e o novo tamanho da fila, para acompanharmos a evolução. private void clean(Date date) {
long difference;
boolean delete;
if (deque.size()==0) {
return;
}
delete=false;
...
Programa Exemplo
8. Continuação. ...
do {
Event e = deque.getLast();
difference = date.getTime() –
e.getDate().getTime();
if (difference > 10000) {
System.out.printf("Cleaner: %s\n",e.getEvent());
deque.removeLast();
delete=true;
}
} while (difference > 10000);
if (delete){
System.out.printf("Cleaner: Tamanho da fila: %d\n",
deque.size());
}
}
Programa Exemplo
9. Agora implemente a classe principal. Crie o método main(). public class Main {
public static void main(String[] args) {
10. Crie a fila para armazenar os eventos usando a classe Deque. Deque<Event> deque=new ArrayDeque<Event>();
11. Crie e inicialize três threads WriterTask e uma CleanerTask. WriterTask writer=new WriterTask(deque);
for (int i=0; i<3; i++){
Thread thread=new Thread(writer);
thread.start();
}
CleanerTask cleaner=new CleanerTask(deque);
cleaner.start();
Programa Exemplo
12. Execute o programa e veja os resultados.
Funcionamento
• Se analisarmos a saída de uma execução do programa, poderemos ver como a fila começa a crescer até que ela tenha 30 eventos e, em seguida, o seu tamanho varia entre 27 e 30 eventos até o final da execução
• O programa começa com três threads WriterTask, cada thread escreve um evento e dorme durante um segundo
• Após os primeiros 10 segundos, temos 30 threads na fila
• Durante estes 10 segundos CleanerTask vem executando, enquanto as três threads WriterTask estavam dormindo, mas não excluiu qualquer evento, porque todos eles foram gerados menos de 10 segundos atrás
Funcionamento
• Durante o resto da execução, CleanerTask exclui três eventos a cada segundo e as três threads WriterTask
escrevem mais três, portanto, o tamanho da fila varia entre 27 e 30 eventos
• Podemos brincar com o tempo em que as threads WriterTask ficam dormindo: se usarmos um valor menor, vamos ver que CleanerTask tem menos tempo de CPU e o tamanho da fila vai aumentar porque CleanerTask não exclui qualquer evento
Criando e Executando Uma Daemon Thread
• Só podemos chamar o método setDaemon() antes de chamar o método start(), uma vez que a thread está sendo executada, não podemos modificar o seu estado daemon
• Podemos usar o método isDaemon() para verificar se uma thread é uma daemon thread (o método retorna true) ou uma thread do usuário (o método retorna false)
Processando Exceções em Uma Thread
• Existem dois tipos de exceções em Java:
– As exceções verificadas: essas exceções devem ser especificadas na cláusula throws de um método ou capturadas dentro dele, por exemplo, IOException ou ClassNotFoundException
– Exceções não verificadas: essas exceções não têm que ser especificadas ou capturadas, por exemplo, NumberFormatException
• Quando uma exceção verificada é lançada dentro do método run() de um objeto Thread, temos de capturá-la e tratá-la, porque o método run() não aceita uma cláusula throws
Processando Exceções em Uma Thread
• Quando uma exceção não verificada é lançada dentro do método run() de um objeto Thread, o comportamento padrão é escrever o rastreamento da pilha no console e sair do programa
• Felizmente, Java nos fornece um mecanismo para capturar e tratar as exceções não verificadas lançadas em um objeto Thread para evitar que o programa termine
• Vamos aprender este mecanismo usando um exemplo
Programa Exemplo
1. Primeiro, temos de implementar uma classe para tratar as exceções não verificadas. Esta classe deve implementar a interface UncaughtExceptionHandler
e implementar o método uncaughtException() declarado na interface. No nosso caso, chame essa classe de ExceptionHandler e faça o método para gravar informações sobre a Exception e a Thread
que a lançou. Veja o código a seguir.
Programa Exemplo
public class ExceptionHandler implements
UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e)
{
System.out.printf("Uma exceção foi capturada\n");
System.out.printf("Thread: %s\n",t.getId());
System.out.printf("Exception: %s: %s\n",
e.getClass().getName(),e.getMessage());
System.out.printf("Stack Trace: \n");
e.printStackTrace(System.out);
System.out.printf("Thread status: %s\n",
t.getState());
}
}
Programa Exemplo
2. Agora, implemente uma classe que gera uma exceção não verificada. Chame esta classe de Task, especifique que ela implementa a interface Runnable, implemente o método run(), e force a exceção, por exemplo, ao tentar converter um valor de sequência de caracteres em um valor int. public class Task implements Runnable {
@Override
public void run() {
int numero=Integer.parseInt("TTT");
}
}
Programa Exemplo
3. Agora implemente a classe principal do exemplo. Implemente uma classe chamada Main com o método main(). public class Main {
public static void main(String[] args) {
4. Crie um objeto do tipo Task e uma Thread para executá-lo. Defina o manipulador de exceção não verificada usando o método setUncaughtExceptionHandler() e inicie a execução da Thread. Task task=new Task();
Thread thread=new Thread(task);
thread.setUncaughtExceptionHandler(
new ExceptionHandler());
thread.start();
}
}
Programa Exemplo
5. Execute o exemplo e veja os resultados.
Funcionamento
• A exceção é lançada e capturado pelo manipulador que escreve no console as informações sobre a exceção e a Thread que a lançou
• Quando uma exceção é lançada em uma thread e não é capturada (tem que ser uma exceção não verificada), a JVM verifica se a thread tem um manipulador de exceção não capturada definido pelo método correspondente, se tiver, a JVM invoca este método com os objetos Thread e Exception como argumentos
• Se a thread não possui um manipulador de exceção não capturada, o JVM imprime o rastreamento da pilha no console e sai do programa
Processando Exceções em Uma Thread
• A classe Thread tem outro método relacionado com o processo de exceções, ele é o método estático setDefaultUncaughtExceptionHandler() que estabelece um manipulador de exceção para todos os objetos Thread da aplicação
Processando Exceções em Uma Thread
• Quando uma exceção não detectada é lançada em Thread, a JVM procura por três manipuladores possíveis para essa exceção:
– Primeiro, ele busca pelo manipulador de exceção não capturada dos objetos Thread como aprendemos neste exemplo
– Se o manipulador não existe, então a JVM busca pelo manipulador de exceção não capturada do ThreadGroup dos objetos Thread como veremos adiante
– Se esse método não existe, a JVM busca pelo manipulador de exceção não capturada padrão
– Se nenhum deles existe, a JVM imprime o rastreamento da pilha da exceção no console e sai do programa
Utilizando Variáveis de Thread Locais
• Um dos aspectos mais críticos de uma aplicação concorrente é o compartilhamento de dados
• Isto tem uma importância especial naqueles objetos que estendem a classe Thread ou implementam a interface Runnable
• Se você criar um objeto de uma classe que implementa a interface Runnable e, em seguida, iniciar vários objetos Thread usando o mesmo objeto Runnable, todos as threads compartilham os mesmos atributos
• Isto significa que, se você alterar um atributo em uma thread, todas as threads serão afetados por esta mudança
Utilizando Variáveis de Thread Locais
• Às vezes, podemos estar interessados em ter um atributo que não será compartilhado entre todas as threads que executam o mesmo objeto
• A API de Concorrência Java fornece um mecanismo limpo chamado de variáveis de threads locais com um desempenho muito bom
• Neste exemplo, iremos desenvolver um programa que tem o problema mostrado no início e outro programa que resolve esse problema usando o mecanismo de variáveis de threads locais
Programa Exemplo
1. Primeiro, vamos implementar um programa que tem o problema exposto anteriormente. Crie uma classe chamada UnsafeTask e especifique que ela implementa a interface Runnable. Declare um atributo java.util.Date privado. public class UnsafeTask implements
Runnable{
private Date startDate;
2. Implemente o método run() do objeto UnsafeTask. Este método irá inicializar o atributo startDate, escrever o seu valor no console, esperar por um período de tempo aleatório, e novamente escrever o valor do atributo startDate.
Programa Exemplo
@Override
public void run() {
startDate=new Date();
System.out.printf(“Iniciando a Thread: %s :
%s\n",Thread.currentThread().getId(),
startDate);
try {
TimeUnit.SECONDS.sleep((int)Math.rint(
Math.random()*10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finalizada: %s :
%s\n",Thread.currentThread().getId(),
startDate);
}
Programa Exemplo
3. Agora, vamos implementar a classe principal desta aplicação problemática. Crie uma classe chamada Main com um método main(). Este método irá criar um objeto da classe UnsafeTask e iniciar três threads usando esse objeto, dormindo por 2 segundos entre cada thread. public class Main {
public static void main(String[] args) {
UnsafeTask task=new UnsafeTask();
for (int i=0; i<3; i++){
Thread thread=new Thread(task);
thread.start();
try { TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
} } }
Programa Exemplo
4. Na captura de tela seguinte, podemos ver os resultados da execução deste programa. Cada thread tem uma hora de início diferentes, mas, quando terminam, todas têm o mesmo valor no seu atributo startDate.
Programa Exemplo
5. Como mencionado anteriormente, iremos usar o mecanismo de variáveis de thread locais para resolver este problema.
6. Crie uma classe chamada SafeTask e especifique que ela implementa a interface Runnable. public class SafeTask implements Runnable {
7. Declare um objeto da classe ThreadLocal<Date>. Este objeto possuirá uma implementação implícita que inclui o método initialValue(). Este método irá retornar a data atual. private static ThreadLocal<Date> startDate=
new ThreadLocal<Date>() {
protected Date initialValue(){
return new Date();
}
};
Programa Exemplo
8. Implemente o método run(). Este possui a mesma funcionalidade do método run() de UnsafeClass, mas modifica a maneira como acessa o atributo startDate. @Override
public void run() {
System.out.printf("Iniciando Thread: %s :
%s\n",Thread.currentThread().getId(),
startDate.get());
try {
TimeUnit.SECONDS.sleep((int)Math.rint(
Math.random()*10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finalizada: %s :
%s\n",Thread.currentThread().getId(),
startDate.get());
}
Programa Exemplo
9. A classe principal deste exemplo é a mesma que a do exemplo inseguro, mudando o nome da classe Runnable.
10. Execute o exemplo e analise a diferença.
Funcionamento
• Agora, os três objetos Thread têm o seu próprio valor do atributo startDate
• As variáveis de thread locais armazenam um valor de um atributo para cada Thread que usa uma destas variáveis
• Podemos ler o valor usando o método get() e alterar o valor usando o método set()
• A primeira vez que acessamos o valor de uma variável de thread local, se ela não tem nenhum valor para o objeto Thread que está chamando, a variável de thread local chama o método initialValue() para atribuir um valor para essa Thread e retorna o valor inicial
Utilizando Variáveis de Thread Locais
• A classe de thread local também fornece o método remove(), que exclui o valor armazenado na variável de thread local para a thread que está chamando
• A API de Concorrência Java inclui a classe InheritableThreadLocal que fornece herança de valores para threads criadas a partir de outra thread
• Se uma thread A tem um valor em uma variável de thread local e criamos outra thread B, a thread B terá o mesmo valor que a thread A na variável de thread local
• Podemos sobrescrever o método childValue() que é chamado para inicializar o valor da thread filha na variável de thread local, ele recebe o valor da thread pai na variável de thread local como parâmetro
Agrupando Threads
• Uma funcionalidade interessante oferecida pela API de Concorrência Java é a capacidade de agrupar as threads
• Isto permite-nos tratar as threads de um grupo como uma unidade, proporcionando acesso aos objetos Thread que pertencem a um grupo e realizar uma operação com elas
• Por exemplo, temos algumas threads que fazem a mesma tarefa e queremos controlá-las, independentemente de quantas threads ainda estão em execução, o estado de cada uma irá interromper todas elas com uma única chamada
Agrupando Threads
• Java fornece a classe ThreadGroup para trabalhar com grupos de threads
• Um objeto ThreadGroup pode ser formado por objetos Thread e por outro objeto ThreadGroup, gerando uma estrutura de árvore de threads
• Neste exemplo, trabalharemos com objetos ThreadGroup desenvolvendo um exemplo simples: teremos 10 threads dormindo durante um período de tempo aleatório (simulando uma busca, por exemplo) e, quando um deles acabar, iremos interromper o resto
Programa Exemplo
1. Primeiro, crie uma classe chamada Result. Ele irá armazenar o nome da Thread que termina em primeiro lugar. Declare um atributo privado String chamado name e os métodos para ler e definir o valor.
2. Crie uma classe chamada SeachTask e especifique que ela implementa a interface Runnable. public class SearchTask implements Runnable {
3. Declare um atributo privado da classe Result e implemente o construtor da classe que inicializa este atributo. private Result result;
public SearchTask(Result result) {
this.result=result;
}
Programa Exemplo
4. Implemente o método run(). Ele vai chamar o método doTask() e esperar que ele termine ou por uma exceção InterruptedException. O método gravará mensagens para indicar o início, fim ou interrupção desta Thread. @Override
public void run() {
...
Programa Exemplo
@Override
public void run() {
String name=Thread.currentThread().getName();
System.out.printf("Thread %s:
Iniciada\n",name);
try {
doTask();
result.setName(name);
} catch (InterruptedException e) {
System.out.printf("Thread %s:
Interrompida\n",name);
return;
}
System.out.printf("Thread %s:
Terminada\n",name);
}
Programa Exemplo
5. Implemente o método doTask(). Ele irá criar um objeto Random para gerar um número aleatório e chamará o método sleep() com o número gerado. private void doTask() throws
InterruptedException {
Random random=new Random((new Date())
.getTime());
int value=(int)(random.nextDouble()*100);
System.out.printf("Thread %s: %d\n",
Thread.currentThread().getName(),
value);
TimeUnit.SECONDS.sleep(value);
}
Programa Exemplo
6. Agora crie a classe principal do exemplo e implemente o método main(). public class Main {
public static void main(String[] args) {
7. Primeiro, crie um objeto ThreadGroup e o chame de Searcher. ThreadGroup threadGroup = new
ThreadGroup("Searcher");
8. Então, crie um objeto SearchTask e um objeto Result. Result result=new Result();
SearchTask searchTask = new
SearchTask(result);
Programa Exemplo
9. Agora, crie 10 objetos Thread usando o objeto SearchTask. Quando você chamar o construtor da classe Thread, passe como primeiro argumento o objeto ThreadGroup. for (int i=0; i<10; i++) {
Thread thread=new Thread(threadGroup,
searchTask);
thread.start();
try { TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Programa Exemplo
10. Imprima informação sobre o objeto ThreadGroup usando o método list(). System.out.printf("Número de Threads: %d\n",
threadGroup.activeCount());
System.out.printf("Informação sobre o Grupo
de Threads\n");
threadGroup.list();
Programa Exemplo
11. Use os métodos activeCount() e enumerate() para saber quantos objetos Thread estão associados ao objeto ThreadGroup e obter uma lista deles. Podemos usar esse método para obter, por exemplo, o estado de cada Thread. Thread[] threads=new
Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
for (int i=0; i<threadGroup.activeCount();
i++) {
System.out.printf("Thread %s: %s\n",
threads[i].getName(),
threads[i].getState());
}
Programa Exemplo
12. Chame o método waitFinish(). Iremos implementá-lo adiante. Ele irá esperar até que uma das threads do objeto ThreadGroup termine. waitFinish(threadGroup);
13. Interrompa o restante das threads do grupo usando o método interrupt(). threadGroup.interrupt();
Programa Exemplo
14. Implemente o método waitFinish(). Ele irá usar o método activeCount() para controlar o final de uma das threads. private static void waitFinish(ThreadGroup
threadGroup) {
while (threadGroup.activeCount()>9) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Programa Exemplo
15. Execute o exemplo e veja o resultado.
Programa Exemplo
15. Execute o exemplo e veja o resultado.
Programa Exemplo
15. Execute o exemplo e veja o resultado.
Funcionamento
• Na captura de tela, podemos ver a saída do método list() e a saída gerada quando escrevemos o estado de cada objeto Thread
• A classe ThreadGroup armazena os objetos Thread e outros objetos ThreadGroup associados a ela, de modo que ela pode acessar todas as suas informações (estado, por exemplo) e executar operações sobre todos os seus membros (interromper, por exemplo)
• A classe ThreadGroup possiu mais métodos, verifique a documentação da API para ter uma explicação completa de todos esses métodos
Processando Exceções em Um Grupo de Threads
• Um aspecto muito importante em toda linguagem de programação é o mecanismo que fornece gerenciamento de situações de erro na aplicação
• A linguagem Java, como quase todas as linguagens de programação modernas, implementa um mecanismo baseado em exceção para gerir situações de erro
• Ela fornece uma série de classes para representar erros diferentes
• Essas exceções são lançadas pelas classes Java quando uma situação de erro é detectada
• Podemos usar essas exceções, ou implementar nossas próprias exceções, para gerenciar os erros produzidos em nossas classes
Processando Exceções em Um Grupo de Threads
• Java também fornece um mecanismo para capturar e processar essas exceções
• Há exceções que devem ser capturados ou re-lançadas usando a cláusula throws de um método, essas exceções são chamados de exceções verificadas
• Há exceções que não têm de ser especificadas ou capturados, estas são as exceções não verificadas
• No tópico, Controlando a Interrupção de uma Thread, vimos como usar um método genérico para processar todas as exceções não verificadas que são lançadas em um objeto Thread
• Outra possibilidade é estabelecer um método que captura todas as exceções não capturadas, lançadas por qualquer Thread da classe ThreadGroup
Programa Exemplo
1. Em primeiro lugar, temos de estender a classe ThreadGroup criando uma classe chamada MyThreadGroup. Temos de declarar um construtor com um parâmetro, porque a classe ThreadGroup não tem um construtor sem ele. public class MyThreadGroup extends
ThreadGroup {
public MyThreadGroup(String name) {
super(name);
}
Programa Exemplo
2. Substitua o método uncaughtException(). Este método é chamado quando uma exceção é lançada em uma das threads da classe ThreadGroup. Neste caso, este método irá escrever no console informações sobre a exceção e da Thread que a lança, e interromper o restante das threads na classe ThreadGroup. @Override
public void uncaughtException(Thread t,
Throwable e) {
System.out.printf("A thread %s lançou uma
Exceção\n",t.getId());
e.printStackTrace(System.out);
System.out.printf("Terminando o restante das
Threads\n");
interrupt();
}
Programa Exemplo
3. Crie uma classe chamada Task e especifique que ele implementa a interface Runnable. public class Task implements Runnable {
4. Implemente o método run(). Neste caso, vamos provocar uma exceção AritmethicException. Para isso, vamos dividir 1.000 entre números aleatórios até que o gerador random gere um zero e a exceção seja lançada. @Override
public void run() {
...
Programa Exemplo
@Override
public void run() {
int result;
Random random=new Random(Thread.currentThread()
.getId());
while (true) {
result=1000/((int)(random.nextDouble()
*1000));
System.out.printf("%s : %d\n", Thread
.currentThread().getId(),result);
if (Thread.currentThread().isInterrupted()) {
System.out.printf("%d : Interrompida\n",Thread.
currentThread().getId());
return;
}
}
}
Programa Exemplo
5. Agora, vamos implementar a classe principal do exemplo e o método main(). public class Main {
public static void main(String[] args) {
6. Crie um objeto da classe MyThreadGroup. MyThreadGroup threadGroup=new
MyThreadGroup("MyThreadGroup");
7. Crie um objeto da classe Task. Task task=new Task();
Programa Exemplo
8. Crie dois objetos Thread com esta Task e inicie-os. for (int i=0; i<2; i++){
Thread t=new Thread(threadGroup,task);
t.start();
}
9. Execute o exemplo e veja os resultados.
Funcionamento
• Quando executamos o exemplo, vemos como um dos objetos Thread lança a exceção e o outro é interrompido
• Quando uma exceção não detectada é lançada em Thread, a JVM procura três possíveis manipuladores para essa exceção:
– Primeiro, ela busca pelo manipulador de exceção não capturada da thread, como foi explicado em tópico anterior
– Se o manipulador não existe, então a JVM busca pelo manipulador de exceção não capturada da classe ThreadGroup da thread, como vimos neste exemplo
– Se esse método não existe, a JVM busca pelo manipulador de exceção não capturada padrão
• Se nenhum dos manipuladores existir, a JVM escreve o rastreamento da pilha de exceção no console e sai do programa
Criando Threads Através de Uma Fábrica
• O padrão fábrica de objetos é um dos design patterns mais utilizados no mundo da programação orientada a objetos
• É um padrão criacional e seu objetivo é desenvolver um objeto cuja missão será a criação de outros objetos de uma ou de várias classes
• Então, quando nós queremos criar um objeto de uma dessas classes, usamos a fábrica em vez de usar o operador new
• Com esta fábrica, nós centralizamos a criação de objetos com algumas vantagens: – É fácil alterar a classe dos objetos criados ou a forma como criar esses
objetos
– É fácil limitar a criação de objetos para recursos limitados, por exemplo, nós só podemos ter n objetos de um tipo
– É fácil gerar dados estatísticos sobre a criação dos objetos
Criando Threads Através de Uma Fábrica
• Java fornece uma interface, a interface ThreadFactory
para implementar uma fábrica de objetos Thread
• Alguns utilitários avançados da API de concorrência Java usam fábricas de threads para criar threads
• Neste exemplo, vamos implementar uma interface ThreadFactory para criar objetos Thread com um nome personalizado, enquanto iremos salvar as estatísticas dos objetos Thread criados
Programa Exemplo
1. Crie uma classe chamada MyThreadFactory e especifique que ele implementa a interface ThreadFactory. public class MyThreadFactory implements
ThreadFactory
2. Declare três atributos: um número inteiro chamado de counter, que vamos usar para armazenar o número do objeto Thread criado, uma String denominada name
com o nome base de cada Thread criada, e uma lista (List) de objetos String chamada stats para salvar os dados estatísticos sobre os objetos Thread criados. Devemos também implementar o construtor da classe que inicializa esses atributos.
Programa Exemplo
private int counter;
private String name;
private List<String> stats;
public MyThreadFactory(String name){
counter=0;
this.name=name;
stats=new ArrayList<String>();
}
Programa Exemplo
3. Implementar o método newThread(). Este método irá receber uma interface Runnable e retorna um objeto Thread para esta interface Runnable. No nosso caso, nós geramos o nome do objeto Thread, criamos o novo objeto Thread, e salvamos as estatísticas. @Override
public Thread newThread(Runnable r) {
Thread t=new Thread(r,name+"-Thread_"
+counter);
counter++;
stats.add(String.format("Thread %d criada
com o nome %s em %s\n",t.getId(),
t.getName(),new Date()));
return t;
}
Programa Exemplo
4. Implemente o método getStatistics() que retorna um objeto String com os dados estatísticos de todos os objetos Thread criados. public String getStats(){
StringBuffer buffer=new StringBuffer();
Iterator<String> it = stats.iterator();
while (it.hasNext()) {
buffer.append(it.next());
}
return buffer.toString();
}
Programa Exemplo
5. Crie uma classe chamada Task e especifique que ela implementa a interface Runnable. Neste exemplo, essas tarefas não vão fazer nada além que dormir por um segundo. public class Task implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Programa Exemplo
6. Crie a classe principal do exemplo e implemente o método main(). public class Main {
public static void main(String[] args) {
7. Crie um objeto MyThreadFactory e um objeto Task. MyThreadFactory factory=new
MyThreadFactory("MyThreadFactory");
Task task=new Task();
8. Crie 10 objetos Thread usando o objeto MyThreadFactory e os inicie. Thread thread;
System.out.printf("Iniciando as Threads\n");
for (int i=0; i<10; i++){
thread=factory.newThread(task);
thread.start();
}
Programa Exemplo
9. Escreva no console as estatísticas da fabrica de threads. System.out.printf("Estatísticas:\n");
System.out.printf("%s\n",factory.getStats());
10. Execute o exemplo e veja os resultados.
Funcionamento
• A interface ThreadFactory tem apenas um método chamado newThread
• Ele recebe um objeto Runnable como parâmetro e retorna um objeto Thread
• Quando implementamos uma interface ThreadFactory, temos que implementar essa interface e substituir esse método
• A maioria das ThreadFactory básicas, têm apenas uma única linha: return new Thread(r);
Funcionamento
• Podemos melhorar esta aplicação, adicionando algumas variantes: – Criando threads personalizadas, como no exemplo, usando um
formato especial para o nome ou até mesmo criando nossa própria classe Thread que herda da classe Thread Java
– Salvando estatísticas de criação de threads, como mostrado no exemplo anterior
– Limitando o número de threads criadas
– Validando a criação das threads
– E mais o que possamos imaginar
• O uso do padrão de design de fábrica é uma boa prática de programação, mas, se implementarmos uma interface ThreadFactory para centralizar a criação de threads, temos que rever o código para garantir que todas as threads são criadas usando essa fábrica
UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO CONCORRENTE – 2015.1
Fábio M. Pereira