SlideShare a Scribd company logo
1 of 137
Download to read offline
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)
Roteiro
• Sincronizando um Método
• Atributos Independentes em Classes Sincronizadas
• Usando Condições em Código Sincronizado
• Sincronizando um Bloco de Código com um Bloqueio
• Sincronizando Acesso a Dados com Bloqueios de
Leitura/Escrita
• Modificando o Equilíbrio entre Bloqueios
• Usando Múltiplas Condições em um Bloqueio
Introdução
• Uma das situações mais comuns em programação
concorrente ocorre quando mais de uma thread de execução
compartilha um recurso
• Em um aplicativo concorrente, é normal que várias threads
leiam ou escrevam os mesmos dados ou tenham acesso ao
mesmo arquivo ou conexão com o banco de dados
• Esses recursos compartilhados podem provocar situações de
erro ou inconsistência de dados e temos que implementar
mecanismos para evitar esses erros
• A solução para estes problemas é fornecida com o conceito
de seção crítica
• A seção crítica é um bloco de código que acessa um recurso
compartilhado e não pode ser executado por mais de uma
thread, ao mesmo tempo.
Introdução
• Para ajudar os programadores a implementar seções
críticas, Java (e quase todas as linguagens de
programação) oferece mecanismos de sincronização
• Quando uma thread quer acesso a uma seção crítica, ela
usa um desses mecanismos de sincronização para saber
se há alguma outra thread em execução na seção crítica
• Se não há, a thread entra na seção crítica, caso contrário,
a thread é suspensa pelo mecanismo de sincronização
até que a thread que está executando a seção crítica
termine
• Quando mais de uma thread está aguardando uma outra
thread terminar a execução de uma seção crítica, a JVM
escolhe uma delas, e o restante esperar pela sua vez
Introdução
• Esta aula apresenta uma série de exemplos que mostram
como usar os dois mecanismos de sincronização básicos
oferecidos pela linguagem Java:
– A palavra-chave synchronized
– A interface Lock e suas implementações
Sincronizando um Método
• Veremos como usar um dos métodos mais básicos para a
sincronização em Java, isto é, o uso da palavra-chave
synchronized para controlar o acesso simultâneo a um
método
• Apenas uma thread de execução irá acessar um dos métodos
de um objeto declarado com a palavra-chave synchronized
• Se outra thread tenta acessar qualquer método declarado
com a palavra-chave synchronized do mesmo objeto, ele
será suspenso até que a primeira thread termine a execução
do método
• Em outras palavras, cada método declarado com a palavra-
chave synchronized é uma seção crítica e Java só permite a
execução de uma das seções críticas de um objeto
Sincronizando um Método
• Os métodos estáticos possuem um comportamento
diferente: apenas uma thread de execução irá acessar um
dos métodos estáticos declarados com a palavra-chave
synchronized, mas outra thread pode acessar outros
métodos não-estáticos de um objeto dessa classe
• Temos que ter muito cuidado com este ponto, porque
duas threads podem acessar dois métodos
synchronized diferentes se um é estático e o outro não
é
• Se ambos os métodos alterar os mesmos dados,
podemos ter erros de inconsistência de dados
Sincronizando um Método
• Para aprender este conceito, vamos implementar um
exemplo com duas threads acessando um objeto comum
• Vamos ter uma conta bancária e duas threads: uma que
transfere dinheiro para a conta e outra que retira
dinheiro da conta
– Sem métodos de sincronização, poderíamos ter resultados
incorretos
– Mecanismos de sincronização garantem que o saldo final da
conta esteja correto
Programa Exemplo
1. Crie uma classe chamada Account que irá modelar nossa
conta bancária. Ela possui um único atributo double,
chamado balance (saldo).
public class Account {
private double balance;
2. Implemente os métodos setBalance() e getBalance()
para escrever e ler o valor do atributo.
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
Programa Exemplo
3. Implementar um método chamado addAmount() que aumenta
o valor do saldo em uma certa quantidade que é passada para o
método. Apenas uma thread deve alterar o valor do saldo,
portanto, use a palavra-chave synchronized para converter
este método em uma seção crítica.
public synchronized void addAmount(double
amount) {
double tmp=balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp+=amount;
balance=tmp;
}
Programa Exemplo
4. Implementar um método chamado subtractAmount() que
diminui o valor do saldo em uma certa quantidade que é passada
para o método. Apenas uma thread deve alterar o valor do saldo,
portanto, use a palavra-chave synchronized para converter
este método em uma seção crítica.
public synchronized void subtractAmount(double
amount) {
double tmp=balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp-=amount;
balance=tmp;
}
Programa Exemplo
5. Implemente uma classe que simula um caixa eletrônico
(ATM). Ele vai usar o método subtractAmount() para
diminuir o saldo de uma conta. Esta classe deve
implementar a interface Runnable a ser executada
como uma thread.
public class Bank implements Runnable {
6. Adicione um objeto Account a esta classe. Implemente
o construtor da classe que inicializa o objeto Account.
private Account account;
public Bank(Account account) {
this.account=account;
}
Programa Exemplo
7. Implemente o método run(). Ele faz 100 chamadas ao
método subtractAmount() de uma conta para reduzir o
saldo.
@Override
public void run() {
for (int i=0; i<100; i++){
account.subtractAmount(1000);
}
}
8. Implementar uma classe que simula uma empresa e utiliza o
método addAmount() da classe Account para incrementar
o saldo da conta. Esta classe deve implementar a interface
Runnable a ser executada como uma thread.
public class Company implements Runnable {
Programa Exemplo
9. Adicione um objeto Account a esta classe. Implemente o
construtor da classe que inicializa o objeto account.
private Account account;
public Company(Account account) {
this.account=account;
}
10. Implemente o método run(). Ele faz 100 chamadas ao
método addAmount() de uma conta para incrementar o
saldo.
@Override
public void run() {
for (int i=0; i<100; i++){
account.addAmount(1000);
}
}
Programa Exemplo
11. Implementar a classe principal do aplicativo criando
uma classe principal que contém o método main().
public class Main {
public static void main(String[] args) {
12. Crie um objeto Account e inicialize o seu saldo com
1000.
Account account=new Account();
account.setBalance(1000);
13. Crie um objeto Company e uma Thread para executá-lo.
Company company=new Company(account);
Thread companyThread=new Thread(company);
Programa Exemplo
14. Crie um objeto Bank e uma Thread para executá-lo.
Bank bank=new Bank(account);
Thread bankThread=new Thread(bank);
15. Escreva o saldo inicial no console. Inicie as threads.
System.out.printf("Conta : Saldo Inicial:
%fn",account.getBalance());
companyThread.start();
bankThread.start();
Programa Exemplo
16. Aguarde a finalização das duas threads usando o
método join() e imprima no console o saldo final da
conta.
try {
companyThread.join();
bankThread.join();
System.out.printf("Conta : Saldo Final:
%fn",account.getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
}
Funcionamento
• Neste exemplo, desenvolvemos um aplicativo que
incrementa e diminui o saldo de uma classe que simula
uma conta bancária
• O programa faz 100 chamadas ao método addAmount()
que incrementa o saldo em 1.000 em cada chamada e
100 chamadas ao método subtractAmount() que
diminui o saldo em 1.000 em cada chamada
• Devemos esperar que os saldos inicial e final sejam iguais
Funcionamento
• Tentamos forçar uma situação de erro usando uma
variável chamada tmp para armazenar o valor do saldo da
conta, de modo que lemos o saldo da conta,
incrementamos o valor da variável temporária e então
estabelecemos o valor do saldo da conta novamente
• Além disso, introduzimos um pouco de atraso, utilizando
o método sleep() da classe Thread para colocar a
thread que está executando o método para dormir por 10
milissegundos, portanto, se outra thread executar esse
método, ela pode modificar o equilíbrio da conta,
provocando um erro
• É o mecanismo da palavra-chave synchronized que
evita esses erros
Funcionamento
• Se quisermos ver os problemas de acesso concorrente
aos dados compartilhados, basta apagarmos a palavra-
chave synchronized dos métodos addAmount() e
subtractAmount() e executarmos o programa
• Sem a palavra-chave synchronized, enquanto uma
thread está dormindo após a leitura do valor do saldo da
conta, outro método irá ler o saldo da conta, então
ambos os métodos irão modificar o mesmo saldo e uma
das operações não será refletida no resultado final
Funcionamento
• Como podemos ver na captura de tela seguinte, podemos
obter resultados inconsistentes:
Funcionamento
• Se executarmos o programa várias vezes, obteremos
resultados diferentes
• A ordem de execução das threads não é garantida pela
JVM, então, toda vez que executarmos o programa, as
threads vão ler e modificar o saldo da conta em uma
ordem diferente, de modo que o resultado final será
diferente
• Agora, adicione a palavra-chave synchronized como
vimos antes e execute o programa novamente
Funcionamento
• Como podemos ver na imagem seguinte, agora obtemos
o resultado esperado
• Se você executar o programa várias vezes, iremos obter o
mesmo resultado
• Veja a imagem a seguir:
Funcionamento
• Usando a palavra-chave synchronized, nós garantimos
o acesso correto para os dados compartilhados em
aplicações concorrentes
• Como mencionado na introdução, apenas uma thread
pode acessar os métodos de um objeto que use a
palavra-chave synchronized na sua declaração
• Se uma thread (A) está executando um método
synchronized e outra thread (B), quer executar outro
métodos synchronized do mesmo objeto, ela vai ser
bloqueada até que a thread (A) termine
• Mas se threadB tem acesso a diferentes objetos da
mesma classe, nenhum deles será bloqueado
Sincronizando um Método
• A palavra-chave synchronized penaliza o desempenho da
aplicação, portanto, devemos usá-la apenas sobre os métodos
que modificam os dados compartilhados em um ambiente
concorrente
• Se temos várias threads que chamam um método
synchronized, apenas um irá executá-los em um momento
enquanto os outros vão estar à espera
• Se essa operação não utilizar a palavra-chave synchronized,
todos as threads podem executar a operação, ao mesmo
tempo, reduzindo o tempo total de execução
• Se sabemos que um método não será chamado por mais de
uma thread, não devemos usar a palavra-chave
synchronized
Sincronizando um Método
• Podemos usar chamadas recursivas com métodos
synchronized
• À medida que a thread tem acesso aos métodos
synchronized de um objeto, podemos chamar outros
métodos synchronized desse objeto, incluindo o
método que está sendo executado, ela não terá que
conseguir acesso aos métodos synchronized
novamente
Sincronizando um Método
• Podemos usar a palavra-chave synchronized para
proteger o acesso a um bloco de código em vez de um
método inteiro
• Devemos utilizar a palavra-chave synchronized desta
forma para proteger o acesso aos dados compartilhados,
deixando o restante das operações de fora do bloco,
obtendo-se um melhor desempenho da aplicação
synchronized (this) {
// Java code
}
Sincronizando um Método
• O objetivo é fazer com que a seção crítica (o bloco de código
que só pode ser acessado por uma thread por vez) seja tão
curta quanto possível
• Temos usado a palavra-chave synchronized para proteger o
acesso à instrução que atualiza o número de pessoas no
prédio, deixando de fora as longas operações deste bloco que
não usam os dados compartilhados
• Quando usamos a palavra-chave synchronized dessa
maneira, devemos passar uma referência de objeto como um
parâmetro
• Apenas uma thread pode acessar o código synchronized
(blocos ou métodos) desse objeto
• Normalmente, vamos usar a palavra-chave this para
referenciar o objeto que está executando o método
Atributos Independentes em Classes
Sincronizadas
• Quando usamos a palavra-chave synchronized para
proteger um bloco de código, devemos passar uma referência
de objeto como um parâmetro
• Normalmente, iremos usar a palavra-chave this para
referenciar o objeto que executa o método, mas podemos
usar outras referências de objeto
• Normalmente, esses objetos serão criados exclusivamente
com esta finalidade
• Por exemplo, se temos dois atributos independentes em uma
classe compartilhada por várias threads, devemos sincronizar
o acesso a cada variável, mas não há nenhum problema se
houver uma thread acessando um dos atributos e outra
thread acessando o outro, ao mesmo tempo
Atributos Independentes em Classes
Sincronizadas
• Neste exemplo, iremos aprender como resolver esta
situação com um programa que simula um cinema com
duas telas e duas bilheterias
• Quando uma bilheteria vende bilhetes, eles são para um
dos dois filmes, mas não para ambos, então o número de
assentos livres em cada sala são atributos independentes
Programa Exemplo
1. Crie uma classe chamada Cinema e adicione dois
atributos long chamados vacanciesCinema1 e
vacanciesCinema2.
public class Cinema {
private long vacanciesCinema1;
private long vacanciesCinema2;
2. Adicione à classe Cinema dois atributos Object
adicionais chamados controlCinema1 e
controlCinema2.
private final Object controlCinema1,
controlCinema2;
Programa Exemplo
3. Implemente o construtor da classe Cinema que inicializa
todos os atributos da classe.
public Cinema(){
controlCinema1=new Object();
controlCinema2=new Object();
vacanciesCinema1=20;
vacanciesCinema2=20;
}
Programa Exemplo
4. Implemente o método sellTickets1() que é chamado
quando alguns bilhetes para o primeiro cinema são
vendidos. Ele utiliza o objeto controlCinema1 para
controlar o acesso ao bloco de código synchronized.
public boolean sellTickets1 (int number) {
synchronized (controlCinema1) {
if (number<vacanciesCinema1) {
vacanciesCinema1-=number;
return true;
} else {
return false;
}
}
}
Programa Exemplo
5. Implemente o método sellTickets2() que é chamado
quando alguns bilhetes para o segundo cinema são
vendidos. Ele utiliza o objeto controlCinema2 para
controlar o acesso ao bloco de código synchronized.
public boolean sellTickets2 (int number) {
synchronized (controlCinema2) {
if (number<vacanciesCinema2) {
vacanciesCinema2-=number;
return true;
} else {
return false;
}
}
}
Programa Exemplo
6. Implemente o método returnTickets1() que é
chamado quando alguns bilhetes para o primeiro
cinema são devolvidos. Ele utiliza o objeto
controlCinema1 para controlar o acesso ao bloco de
código synchronized.
public boolean returnTickets1 (int number) {
synchronized (controlCinema1) {
vacanciesCinema1+=number;
return true;
}
}
Programa Exemplo
7. Implemente o método returnTickets2() que é
chamado quando alguns bilhetes para o segundo
cinema são devolvidos. Ele utiliza o objeto
controlCinema2 para controlar o acesso ao bloco de
código synchronized.
public boolean returnTickets2 (int number) {
synchronized (controlCinema2) {
vacanciesCinema2+=number;
return true;
}
}
Programa Exemplo
8. Implemente outros dois métodos que retornam o
número de vagas em cada cinema.
public long getVacanciesCinema1() {
return vacanciesCinema1;
}
public long getVacanciesCinema2() {
return vacanciesCinema2;
}
9. Implemente a classe TicketOffice1 e especifique que
ela implementa a interface Runnable.
public class TicketOffice1
implements Runnable {
Programa Exemplo
10. Declare um objeto Cinema e implemente o construtor
da classe que inicializa este objeto.
private Cinema cinema;
public TicketOffice1 (Cinema cinema) {
this.cinema=cinema;
}
Programa Exemplo
11. Implemente o método run() que simula algumas
operações sobre os dois cinemas.
@Override
public void run() {
cinema.sellTickets1(3);
cinema.sellTickets1(2);
cinema.sellTickets2(2);
cinema.returnTickets1(3);
cinema.sellTickets1(5);
cinema.sellTickets2(2);
cinema.sellTickets2(2);
cinema.sellTickets2(2);
}
Programa Exemplo
12. Implemente a classe TicketOffice2 e especifique que
ela implementa a interface Runnable.
public class TicketOffice2
implements Runnable {
13. Declare um objeto Cinema e implemente o construtor
da classe que inicializa este objeto.
private Cinema cinema;
public TicketOffice2 (Cinema cinema) {
this.cinema=cinema;
}
Programa Exemplo
14. Implemente o método run() que simula algumas
operações sobre os dois cinemas.
@Override
public void run() {
cinema.sellTickets2(2);
cinema.sellTickets2(4);
cinema.sellTickets1(2);
cinema.sellTickets1(1);
cinema.returnTickets2(2);
cinema.sellTickets1(3);
cinema.sellTickets2(2);
cinema.sellTickets1(2);
}
Programa Exemplo
15. Implemente a classe principal do exemplo através da criação
de uma classe principal e adicionando-lhe o método main().
public class Main {
public static void main(String[] args) {
16. Declare e crie um objeto Cinema.
Cinema cinema=new Cinema();
17. Crie um objeto TicketOffice1 e a Thread para executá-lo.
TicketOffice1 ticketOffice1=new
TicketOffice1(cinema);
Thread thread1=new
Thread(ticketOffice1,
"TicketOffice1");
Programa Exemplo
18. Crie um objeto TicketOffice2 e a Thread para executá-lo.
TicketOffice2 ticketOffice2=new
TicketOffice2(cinema);
Thread thread2=new
Thread(ticketOffice2,
"TicketOffice2");
19. Inicie ambas as threads.
thread1.start();
thread2.start();
Programa Exemplo
20. Aguarde até que as threads sejam finalizadas.
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
21. Escreva no console as vagas disponíveis nos dois
cinemas.
System.out.printf("Sala 1 Vagas: %dn",
cinema.getVacanciesCinema1());
System.out.printf("Sala 2 Vagas: %dn",
cinema.getVacanciesCinema2());
Funcionamento
• Quando usamos a palavra-chave synchronized para
proteger um bloco de código, podemos usar um objeto
como um parâmetro
• A JVM garante que apenas uma thread pode ter acesso a
todos os blocos de código protegidas com esse objeto
(note que sempre falamos sobre objetos, e não sobre
classes)
Funcionamento
• Neste exemplo, temos um objeto que controla o acesso
ao atributo vacanciesCinema1, portanto, apenas uma
thread pode modificar este atributo de cada vez, e outro
objeto que controla o acesso ao atributo
vacanciesCinema2, portanto, apenas uma thread pode
modificar este atributo de cada vez
• Mas podem haver duas threads em execução
concorrente, uma modificando o atributo
vacancesCinema1 e a outra modificando o atributo
vacanciesCinema2
Funcionamento
• Quando executarmos esse exemplo, podemos ver como o
resultado final é sempre o número esperado de vagas
para cada cinema
• Na captura de tela seguinte, podemos ver os resultados
de uma execução da aplicação:
Usando Condições em Código
Sincronizado
• Um problema clássico na programação concorrente é o
problema do produtor-consumidor
• Temos um buffer de dados, um ou mais produtores de
dados que os salvam no buffer e um ou mais
consumidores de dados que leem a partir do buffer
• À medida que o buffer é uma estrutura de dados
compartilhada, temos que controlar o acesso a ela
através de um mecanismo de sincronização como o da
palavra-chave synchronized, mas que tenha mais
limitações
• Um produtor não pode salvar dados no buffer se ele está
cheio e que o consumidor não pode retirar dados do
buffer se ele está vazio
Usando Condições em Código
Sincronizado
• Para esses tipos de situações, Java fornece os métodos
wait(), notify() e notifyAll() implementado na
classe Object
• Uma thread pode chamar o método wait() dentro de um
bloco de código synchronized
– Se ela chamar o método wait() do lado de fora de um bloco de
código synchronized, a JVM lança uma exceção
IllegalMonitorStateException
• Quando a thread chama o método wait(), a JVM coloca a
thread para dormir e libera o objeto que controla o bloco de
código synchronized que está em execução e permite que
as outras threads executem outros blocos de código
synchronized protegidos por esse objeto
• Para acordar a thread, devemos chamar o método notify()
ou notifyAll() dentro de um bloco de código protegido
pelo mesmo objeto
Usando Condições em Código
Sincronizado
• Neste exemplo, vamos aprender como implementar o
problema do produtor-consumidor, utilizando a palavra-
chave synchronized e os métodos wait(), notify(),
e notifyAll()
Programa Exemplo
1. Crie uma classe chamada EventStorage. Ela tem dois
atributos: um atributo int chamado maxSize e um
atributo LinkedList <Date> chamado storage.
public class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
2. Implemente o construtor da classe que inicializa os seus
atributos.
public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}
Programa Exemplo
3. Implemente o método synchronized set() para armazenar um
evento. Primeiro, verifique se o buffer está cheio ou não. Se ele
estiver cheio, chame o método wait() até que tenha espaço
vazio. No final do método, chamamos o método notifyAll()
para acordar todos as threads que estão dormindo no método
wait().
public synchronized void set(){
while (storage.size()==maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.offer(new Date());
System.out.printf("Set: %dn",storage.size());
notifyAll();
}
Programa Exemplo
4. Implementar o método synchronized get() para obter um
evento do armazenamento. Primeiro, verifique se o buffer tem
eventos ou não. Se ele não tem eventos, chame o método wait()
até que tenha algum evento. No final do método, chamamos o
método notifyAll() para acordar todas as threads que estão
dormindo no método wait().
public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %sn",storage.size(),
((LinkedList<?>)storage).poll());
notifyAll();
}
Programa Exemplo
5. Crie uma classe chamada Producer e especifique que
ela implementa a interface Runnable. Ela irá
implementar o produtor do exemplo.
public class Producer implements Runnable {
6. Declare um objeto EventStorage e implemente o
construtor da classe, que inicializa este objeto.
private EventStorage storage;
public Producer(EventStorage storage){
this.storage=storage;
}
Programa Exemplo
7. Implemente o método run() que chama 100 vezes o
método set() do objeto EventStorage.
@Override
public void run() {
for (int i=0; i<100; i++){
storage.set();
}
}
8. Crie uma classe chamada Consumer e especifique que
ela implementa a interface Runnable. Ela irá
implementar o consumidor para o exemplo.
public class Consumer implements Runnable {
Programa Exemplo
9. Declare um objeto EventStorage e implemente o
construtor da classe que inicializa este objeto.
private EventStorage storage;
public Consumer(EventStorage storage){
this.storage=storage;
}
10. Implemente o método run(). Ele chama 100 vezes o
método get() do objeto EventStorage.
@Override
public void run() {
for (int i=0; i<100; i++){
storage.get();
}
}
Programa Exemplo
11. Crie a classe principal para o exemplo e adicione o
método main().
public class Main {
public static void main(String[] args) {
12. Crie um objeto EventStorage.
EventStorage storage=new EventStorage();
13. Crie um objeto Producer e a Thread para executá-lo.
Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);
Programa Exemplo
14. Crie um objeto Consumer e a Thread para executá-lo.
Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);
15. Inicie ambas as threads.
thread2.start();
thread1.start();
Funcionamento
• A chave para este exemplo são os métodos set() e
get() da classe EventStorage
• Primeiro, o método set() verifica se há espaço livre no
atributo de armazenamento
– Se ele está cheio, ele chama o método wait() para aguardar
espaço livre
• Quando a outra thread chama o método notifyAll(),
a thread acorda e verifica a condição novamente
– O método notifyAll() não garante que a thread vai acordar
• Este processo é repetido até que haja espaço livre no
armazenamento, então ela pode gerar um novo evento e
armazená-lo
Funcionamento
• O comportamento do método get() é semelhante,
primeiro, ele verifica se há eventos no armazenamento
– Se a classe EventStorage está vazia, ele chama o método
wait() para aguardar eventos
– Quando a outra thread chama o método notifyAll(), a thread
acorda e verifica a condição novamente até que existam alguns
eventos no armazenamento
• Temos que manter a verificação das condições e chamar
o método wait() em um laço while, não podemos
continuar até que a condição seja verdadeira
• Se executarmos esse exemplo, vamos ver como o
produtor e o consumidor estão criando e obtendo os
eventos, mas o armazenamento nunca possui mais do de
10 eventos
Sincronizando um Bloco de Código com
um Bloqueio
• Java fornece um outro mecanismo para a sincronização
de blocos de código
• É um mecanismo mais poderoso e flexível do que a
palavra-chave synchronized
• Ele é baseado na interface Lock e classes que a
implementam (como ReentrantLock)
Sincronizando um Bloco de Código com
um Bloqueio
• Este mecanismo apresenta algumas vantagens, que são
as seguintes:
– Ele permite a estruturação de blocos sincronizados de uma
maneira mais flexível. Com a palavra-chave synchronized,
temos que obter e liberar o controle sobre um bloco
sincronizado de código de uma forma estruturada. As interfaces
Lock permitem que obtenhamos estruturas mais complexas
para implementar a seção crítica.
Sincronizando um Bloco de Código com
um Bloqueio
• Vantagens (continuação):
– As interfaces Lock fornecem funcionalidades adicionais sobre a
palavra-chave synchronized. Uma das novas funcionalidades é
implementada pelo método tryLock(). Este método tenta
obter o controle do bloqueio e se não pode, porque ele está
sendo usado por outra thread, ele retorna o bloqueio. Com a
palavra-chave synchronized, quando uma thread (A) tenta
executar um bloco de código sincronizado, se houver outra
thread (B) executando-o, a thread (A) é suspensa até que a
thread (B) termine a execução do bloco sincronizado. Com
bloqueios, podemos executar o método tryLock(). Esse
método retorna um valor booleano que indica se há outra
thread executando o código protegido por esse bloqueio.
Sincronizando um Bloco de Código com
um Bloqueio
• Vantagens (continuação):
– As interfaces Lock permitem uma separação das operações de
leitura e gravação que possuem vários leitores e apenas um
modificador.
– As interfaces Lock oferecer um melhor desempenho do que a
palavra-chave synchronized.
• Neste exemplo, vamos aprender como usar bloqueios
para sincronizar um bloco de código e criar uma seção
crítica usando a interface Lock e a classe ReentrantLock
que a implementa, implementando um programa que
simula uma fila de impressão
Programa Exemplo
1. Crie uma classe chamada PrintQueue que irá
implementar a fila de impressão.
public class PrintQueue {
2. Declare um objeto Lock e o inicialize com um novo
objeto da classe ReentrantLock.
private final Lock queueLock=new
ReentrantLock();
3. Implemente o método printJob(). Ele irá receber um
Object como parâmetro e não irá retornar nenhum
valor.
public void printJob(Object document){
Programa Exemplo
4. Dentro do método printJob() obtenha o controle do
objeto Lock chamando o método lock().
lock() method.
queueLock.lock();
5. Então, inclua o código a seguir para simular a impressão de
um documento:
try {
Long duration=(long)(Math.random()*10000);
System.out.println(Thread.currentThread()
.getName()+ ":Fila de Impressão:
Imprimindo um trabalho durante "+
(duration/1000)+" segundos");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
Programa Exemplo
6. Finalmente, libere o controle do objeto Lock com o método
unlock().
finally {
queueLock.unlock();
}
7. Crie uma classe chamada Job e especifique que ela
implementa a interface Runnable.
public class Job implements Runnable {
8. Declare um objeto da classe PrintQueue e implemente o
construtor da classe que inicializa esse objeto.
private PrintQueue printQueue;
public Job(PrintQueue printQueue){
this.printQueue=printQueue;
}
Programa Exemplo
9. Implemente o método run(). Ele usa o objeto
PrintQueue para enviar um trabalho para a impressora.
@Override
public void run() {
System.out.printf("%s: Imprimindo um
documenton", Thread.currentThread()
.getName());
printQueue.printJob(new Object());
System.out.printf("%s: O documento foi
impresson",Thread.currentThread()
.getName());
}
10. Crie a classe principal da aplicação e adicione o método
main() a ela.
public class Main {
public static void main (String args[]){
Programa Exemplo
11. Crie um objeto compartilhado PrintQueue.
PrintQueue printQueue=new PrintQueue();
12. Cria 10 objetos Job e 10 threads pra executá-los.
Thread thread[]=new Thread[10];
for (int i=0; i<10; i++){
thread[i]=new Thread(new Job(printQueue),
"Thread "+ i);
}
13. Inicie as 10 threads.
for (int i=0; i<10; i++){
thread[i].start();
}
Funcionamento
• Na captura de tela seguinte, podemos ver uma parte da
saída de uma execução, deste exemplo:
Funcionamento
• A chave para o exemplo é o método printJob() da classe
PrintQueue
• Quando queremos implementar uma seção crítica usando
bloqueios e garantir que apenas uma thread de execução
execute um bloco de código, temos que criar um objeto
ReentrantLock
• No início da seção crítica, temos de começar o controle do
bloqueio utilizando o método lock()
• Quando uma thread (A) chama este método, se não houver
outra thread com o controle do bloqueio, o método permite
que a thread (A) controle o bloqueio e retorna imediatamente
para permitir a execução da seção crítica por esta thread
• Caso contrário, se há uma outra thread (B) executando a
seção crítica controlada por este bloqueio, o método lock()
coloca a thread (A) para dormir até que a thread (B) termine a
execução da seção crítica
Funcionamento
• No final da seção crítica, temos de usar o método
unlock() para liberar o controle do bloqueio e permitir
que as outras threads executem esta seção crítica
• Se não chamarmos o método unlock() no final da seção
crítica, as outras threads que estão aguardando por esse
bloco ficarão esperando para sempre, causando uma
situação de impasse (deadlock)
• Se usarmos blocos try-catch em uma seção crítica, não
podemos esquecer de colocar o comando que contém o
método unlock() dentro da seção finally
Sincronizando um Bloco de Código com
um Bloqueio
• A interface Lock (e a classe ReentrantLock) inclui um
outro método para obter o controle do bloqueio, é o
método tryLock()
• A maior diferença com o método lock() é que neste
método, se a thread que o usa não pode obter o controle
da interface Lock, retorna imediatamente e não coloca a
thread para dormir
• Este método retorna um valor booleano, true se a
thread recebe o controle do bloqueio, e false se não
• Leve em consideração que é responsabilidade do
programador ter em conta o resultado deste método e
agir em conformidade
Sincronizando um Bloco de Código com
um Bloqueio
• Se o método retorna o valor false, espera-se que o
programa não execute a seção crítica
• Se isso acontecer, provavelmente teremos resultados
errados na aplicação
Sincronizando um Bloco de Código com
um Bloqueio
• A classe ReentrantLock também permite o uso de
chamadas recursivas
• Quando uma thread tem o controle de um bloqueio e faz
uma chamada recursiva, continua com o controle do
bloqueio, de modo que a chamada ao método lock()
retornará imediatamente e a thread vai continuar com a
execução da chamada recursiva
• Além disso, também pode chamar outros métodos
Sincronizando um Bloco de Código com
um Bloqueio
• Devemos ter muito cuidado com o uso de bloqueios para
evitar impasses (deadlocks)
• Esta situação ocorre quando duas ou mais threads são
bloqueadas à espera de bloqueios que nunca vão ser
desbloqueados
• Por exemplo, uma thread (A) bloqueia um Lock (X) e uma
thread (B) bloqueia um Lock (Y)
• Se agora, a thread (A) tenta bloquear um Lock (Y) e a thread
(B) simultaneamente tenta bloquear um Lock (X), ambas as
threads serão bloqueadas indefinidamente, porque elas estão
à espera de bloqueios que nunca vão ser liberados
• Note-se que o problema ocorre, porque ambas as threads
tentam obter os bloqueios na ordem oposta
Sincronizando Acesso a Dados com
Bloqueios de Leitura/Escrita
• Uma das melhorias mais significativas oferecidos por
bloqueios é a interface ReadWriteLock e a classe
ReentrantReadWriteLock, a única que a implementa
• Essa classe tem dois bloqueios, um para operações de leitura
e um para operações de gravação
• Pode haver mais de uma thread usando operações de leitura
simultaneamente, mas somente uma thread pode estar
usando operações de gravação
• Quando uma thread está fazendo uma operação de gravação,
não pode haver qualquer outra thread fazendo operações de
leitura
• Neste exemplo, vamos aprender como usar uma interface
ReadWriteLock implementando um programa que a utiliza
para controlar o acesso a um objeto que armazena os preços
de dois produtos
Programa Exemplo
1. Crie uma classe chamada PricesInfo que armazenará
informação sobre os preços de dois produtos.
public class PricesInfo {
2. Declare dois atributos double chamados price1 e
price2.
private double price1;
private double price2;
3. Declare um objeto ReadWriteLock chamado lock.
private ReadWriteLock lock;
Programa Exemplo
4. Implemente o construtor da classe que inicializa estes
três atributos. Para o atributo lock, criaremos um novo
objeto ReentrantReadWriteLock.
public PricesInfo(){
price1=1.0;
price2=2.0;
lock=new ReentrantReadWriteLock();
}
Programa Exemplo
5. Implemente o método getPrice1() que retorna o
valor do atributo price1. Ele utiliza o bloqueio de
leitura para controlar o acesso ao valor deste atributo.
public double getPrice1() {
lock.readLock().lock();
double value=price1;
lock.readLock().unlock();
return value;
}
Programa Exemplo
6. Implemente o método getPrice2() que retorna o
valor do atributo price2. Ele utiliza o bloqueio de
leitura para controlar o acesso ao valor deste atributo.
public double getPrice2() {
lock.readLock().lock();
double value=price2;
lock.readLock().unlock();
return value;
}
Programa Exemplo
7. Implemente o método setPrices() que estabelece os
valores dos dois atributos. Ele utiliza o bloqueio de
escrita para controlar o acesso a eles.
public void setPrices(double price1,
double price2) {
lock.writeLock().lock();
this.price1=price1;
this.price2=price2;
lock.writeLock().unlock();
}
Programa Exemplo
8. Crie uma classe chamada Reader e especifique que ela
implementa a interface Runnable. Esta classe
implementa um leitor de valores dos atributos da classe
PricesInfo.
public class Reader implements Runnable {
9. Declare um objeto PricesInfo e implemente o
construtor da classe que inicializa esse objeto.
private PricesInfo pricesInfo;
public Reader (PricesInfo pricesInfo){
this.pricesInfo=pricesInfo;
}
Programa Exemplo
10. Implemente o método run() desta classe. Ele lê 10
vezes o valor dos dois preços.
@Override
public void run() {
for (int i=0; i<10; i++){
System.out.printf("%s: Price 1: %fn",
Thread.currentThread().getName(),
pricesInfo.getPrice1());
System.out.printf("%s: Price 2: %fn",
Thread.currentThread().getName(),
pricesInfo.getPrice2());
}
}
Programa Exemplo
11. Crie uma classe chamada Writer e especifique que ela
implementa a interface Runnable. Esta classe
implementa um modificador dos valores dos atributos
da classe PricesInfo.
public class Writer implements Runnable {
12. Declare um objeto PricesInfo e implemente o
construtor da classe que inicializa esse objeto.
private PricesInfo pricesInfo;
public Writer(PricesInfo pricesInfo){
this.pricesInfo=pricesInfo;
}
Programa Exemplo
13. Implemente o método run(). Ele modifica três vezes o valor dos
dois preços e dorme por dois segundos entre as alterações.
@Override
public void run() {
for (int i=0; i<3; i++) {
System.out.printf("Writer: Tentando
modificar os preços.n");
pricesInfo.setPrices(Math.random()*10,
Math.random()*8);
System.out.printf("Writer: Os preços foram
modificados.n");
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Programa Exemplo
14. Implemente a classe principal do exemplo e crie o método
main().
public class Main {
public static void main(String[] args) {
15. Crie um objeto PricesInfo.
PricesInfo pricesInfo=new PricesInfo();
16. Crie cinco objetos Reader e cinco Threads para executá-
los.
Reader readers[]=new Reader[5];
Thread threadsReader[]=new Thread[5];
for (int i=0; i<5; i++){
readers[i]=new Reader(pricesInfo);
threadsReader[i]=new Thread(readers[i]);
}
Programa Exemplo
17. Crie um objeto Writer e uma Thread para executá-lo.
Writer writer=new Writer(pricesInfo);
Thread threadWriter=new Thread(writer);
18. Inicie as threads.
for (int i=0; i<5; i++){
threadsReader[i].start();
}
threadWriter.start();
Funcionamento
• Na captura de tela seguinte, você pode ver uma parte da
saída de uma execução deste exemplo:
Funcionamento
• Como mencionamos anteriormente, a classe
ReentrantReadWriteLock tem dois bloqueios, um para
operações de leitura e um para operações de gravação
• O bloqueio utilizado em operações de leitura é obtida
com o método readlock() declarado na interface
ReadWriteLock
• Esse bloqueio é um objeto que implementa a interface
Lock, para que possamos usar os métodos lock(),
unlock(), e tryLock()
• O bloqueio utilizado em operações de gravação é obtido
com o método writeLock() declarado na interface
ReadWriteLock
Funcionamento
• Esse bloqueio é um objeto que implementa a interface
Lock, para que possamos usar os métodos lock(),
unlock(), e tryLock()
• É de responsabilidade do programador garantir o uso
correto desses bloqueios, usando-os com os mesmos
propósitos para os quais eles foram projetados
• Quando conseguimos o bloqueio de leitura de uma
interface Lock, não podemos modificar o valor da
variável, caso contrário, provavelmente teríamos erros de
inconsistência de dados
Modificando o Equilíbrio entre
Bloqueios
• O construtor das classes ReentrantLock e
ReentrantReadWriteLock admite um parâmetro
boolean chamado fair que permite controlar o
comportamento de ambas as classes
• O valor false é o valor padrão e é chamado de modo não
justo
– Neste modo, quando existem algumas threads esperando por um
bloqueio (ReentrantLock ou ReentrantReadWriteLock) e o
bloqueio tem que selecionar uma delas para obter o acesso à seção
crítica, ele seleciona uma sem qualquer critério
• O valor true é chamado de modo justo
– Neste modo, quando existem algumas threads esperando por um
bloqueio (ReentrantLock ou ReentrantReadWriteLock) e o
bloqueio tem que selecionar uma delas para obter acesso a uma
seção crítica, ele seleciona a thread que está esperando por mais
tempo
Modificando o Equilíbrio entre
Bloqueios
• Leve em consideração que o comportamento explicado
anteriormente só é usado com os métodos lock() e
unlock()
• Como o método tryLock() não coloca a thread para
dormir, se a interface Lock é usada, o atributo fair não
afeta a sua funcionalidade
• Neste exemplo, vamos modificar o exemplo
implementado em Sincronizando um Bloco de Código
com um Bloqueio para usar esse atributo e ver a
diferença entre os modos justo e não justo
Programa Exemplo
1. Implemente o exemplo explicado em Sincronizando um
Bloco de Código com um Bloqueio.
2. Na classe PrintQueue, modifique a construção do
objeto Lock.
private Lock queueLock=new
ReentrantLock(true);
3. Modifique o método printJob(). Separe o simulador
de impressão em dois blocos de código, liberando o
bloqueio entre eles.
Programa Exemplo
public void printJob(Object document){
queueLock.lock();
try {
Long duration=(long)(Math.random()*10000);
System.out.println(Thread.currentThread().
getName()+":Fila de Impressão:
Imprimindo um trabalho durante "+
(duration/1000)+" segundos");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
...
Programa Exemplo
...
queueLock.lock();
try {
Long duration=(long)(Math.random()*10000);
System.out.println(Thread.currentThread().
getName()+":Fila de Impressão:
Imprimindo um trabalho durante "+
(duration/1000)+" segundos");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
}
Programa Exemplo
4. Modifique a classe principal no bloco que inicia as
threads. O novo bloco deve ser:
for (int i=0; i<10; i++){
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Funcionamento
• Na captura de tela seguinte você pode ver uma parte da
saída de uma execução deste exemplo:
Funcionamento
• Todas as threads são criadas com uma diferença de 0,1
segundo
• A primeira thread que solicita o controle do bloqueio é a
Thread 0, então a Thread 1, e assim por diante
• Enquanto Thread 0 está executando o primeiro bloco de
código protegido pelo bloqueio, temos nove threads em
espera para executar esse bloco de código
• Quando a Thread 0 libera o bloqueio, imediatamente, ela
solicita o bloqueio novamente, por isso temos 10 threads
que tentam obter o bloqueio
• Como o modo justo está habilitado, a interface Lock vai
escolher Thread 1, uma vez que é a thread que está
esperando por mais tempo pelo bloqueio
Funcionamento
• Em seguida, escolhe Thread 2, então, o Thread 3, e assim
por diante
• Até que todas as threads tenham passado pelo primeiro
bloco protegido pelo bloqueio, nenhuma delas vai
executar o segundo bloco protegido
• Uma vez que todas as threads tenham executado o
primeiro bloco de código protegido pelo bloqueio, é a vez
de Thread 0 novamente, em seguida, é a vez de Thread
1, e assim por diante
• Para ver a diferença com o modo não justo, altere o
parâmetro passado para o construtor de bloqueio e
coloque o valor false
Funcionamento
• Na captura de tela seguinte, você pode ver o resultado de
uma execução do exemplo modificado:
Funcionamento
• Neste caso, as threads são executadas pela ordem em
que foram criadas, mas cada thread executa os dois
blocos de código protegidos
• No entanto, esse comportamento não é garantido
porque, como explicado anteriormente, o bloqueio
poderia escolher qualquer thread para dar-lhe acesso ao
código protegido
• A JVM não garante, neste caso, a ordem de execução das
threads
• Bloqueios Read/Write também têm o parâmetro fair em
seu construtor, o comportamento deste parâmetro neste
tipo de bloqueio é o mesmo que foi explicado na
introdução deste exemplo
Usando Múltiplas Condições em um
Bloqueio
• Um bloqueio pode ser associado a uma ou mais
condições
• Estas condições são declaradas na interface Condition
• O objetivo destas condições é permitir que threads
tenham o controle de um bloqueio e verifiquem se uma
condição é verdadeira ou não e, se for falsa, ser suspensa
até que uma outra thread a acorde
• A interface Condition fornece os mecanismos para
suspender uma thread e para acordar uma thread
suspensa
Usando Múltiplas Condições em um
Bloqueio
• Um problema clássico na programação concorrente é o
problema do produtor-consumidor
• Temos um buffer de dados, um ou mais produtores de
dados que armazenam no buffer, e um ou mais
consumidores de dados que leem a partir do buffer como
explicado anteriormente
• Neste exemplo, veremos como implementar o problema
do produtor-consumidor usando bloqueios e condições
Programa Exemplo
1. Primeiro, vamos implementar uma classe que irá
simular um arquivo de texto. Crie uma classe chamada
FileMock com dois atributos: um array String
denominado content e um int chamado index. Eles
vão armazenar o conteúdo do arquivo e a linha do
arquivo simulado que será recuperada.
public class FileMock {
private String content[];
private int index;
Programa Exemplo
2. Implemente o construtor da classe que inicializa o conteúdo
e o arquivo com caracteres aleatórios.
public FileMock(int size, int length){
content=new String[size];
for (int i=0; i<size; i++){
StringBuilder buffer =
new StringBuilder(length);
for (int j=0; j<length; j++){
int indice=(int)Math.random()*255;
buffer.append((char)indice);
}
content[i]=buffer.toString();
}
index=0;
}
Programa Exemplo
3. Implemente o método hasMoreLines() que retorna true
se o arquivo tem mais linhas para processar ou false se já
atingiu o fim do arquivo simulado.
public boolean hasMoreLines(){
return index < content.length;
}
4. Implemente o método getLine() que retorna a linha
determinada pelo atributo index e aumenta o seu valor.
public String getLine(){
if (this.hasMoreLines()) {
System.out.println("Mock: "+
(content.length-index));
return content[index++];
}
return null;
}
Programa Exemplo
5. Agora implemente uma classe chamada Buffer que irá
implementar o buffer compartilhado por produtores e
consumidores.
public class Buffer {
6. Esta classe possui seis atributos:
– Um atributo LinkedList<String> chamado buffer que irá
armazenar os dados compartilhados
– Um int chamado maxSize que armazena o tamanho do buffer
– Um objeto ReentrantLock chamado lock que controla o
acesso aos blocos de código que modificam o buffer
– Dois atributos Condition chamados lines e space
– Um tipo boolean chamado pendingLines que irá indicar se
existem linhas no buffer
Programa Exemplo
private LinkedList<String> buffer;
private int maxSize;
private ReentrantLock lock;
private Condition lines;
private Condition space;
private boolean pendingLines;
Programa Exemplo
7. Implemente o construtor da classe. Ele inicializa todos
os atributos descritos anteriormente.
public Buffer(int maxSize) {
this.maxSize=maxSize;
buffer=new LinkedList<>();
lock=new ReentrantLock();
lines=lock.newCondition();
space=lock.newCondition();
pendingLines=true;
}
Programa Exemplo
8. Implemente o método insert(). Ele recebe uma
String como parâmetro e tenta armazená-lo no buffer.
Em primeiro lugar, ele obtém o controle do bloqueio.
Quando consegue, então verifica se existe espaço vazio
no buffer. Se o buffer está cheio, ele chama o método
await() na condição space para esperar por espaço
livre. A thread será acordada quando outra thread
chamar o método de signal() ou signalAll() na
Condition space. Quando isso acontece, a thread
armazena a linha no buffer e chama o método
signallAll() sobre a condição lines. Como veremos
em um momento, essa condição vai acordar todas as
threads que estão à espera de linhas no buffer.
Programa Exemplo
public void insert(String line) {
lock.lock();
try {
while (buffer.size() == maxSize) {
space.await();
}
buffer.offer(line);
System.out.printf("%s: Linha inserida: %dn",
Thread.currentThread().getName(),
buffer.size());
lines.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
Programa Exemplo
9. Implemente o método get(). Ele retorna a primeira
string armazenada no buffer. Em primeiro lugar, ele
obtém o controle do bloqueio. Quando consegue, ele
verifica se há linhas no buffer. Se o buffer está vazio, ele
chama o método await() na condição lines para
esperar por linhas no buffer. Esta thread será acordada
quando uma outra thread chamar o método signal()
ou signalAll() na condição lines. Quando isso
acontece, o método obtém a primeira linha no buffer,
chama o método signalAll() sobre a condição space
e retorna a String.
Programa Exemplo
public String get() {
String line=null;
lock.lock();
try {
while ((buffer.size() == 0) &&(hasPendingLines())) {
lines.await();
}
if (hasPendingLines()) {
line = buffer.poll();
System.out.printf("%s: Linha carregada: %dn",
Thread.currentThread().getName(),buffer.size());
space.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return line;
}
Programa Exemplo
10. Implemente o método setPendingLines() que
estabelece o valor do atributo pendingLines. Ele será
chamado pelo producer quando não existirem mais
linhas para produzir.
public void setPendingLines(boolean
pendingLines) {
this.pendingLines=pendingLines; }
11. Implemente o método hasPendingLines. Ele retorna
true se existem mais linhas a serem processadas, ou
false caso contrário.
public boolean hasPendingLines() {
return pendingLines || buffer.size()>0;
}
Programa Exemplo
12. Implemente uma classe chamada Producer e especifique
que ela implementa a interface Runnable.
public class Producer implements Runnable {
13. Declare dois atributos: um objeto da classe FileMock e
outro objeto da classe Buffer.
private FileMock mock;
private Buffer buffer;
14. Implemente o construtor da classe que inicializa ambos os
atributos.
public Producer (FileMock mock, Buffer buffer){
this.mock=mock;
this.buffer=buffer;
}
Programa Exemplo
15. Implemente o método run() que lê todas as linhas criadas
no objeto FileMock e usa o método insert() para
armazená-las no buffer. Uma vez terminado, use o método
setPendingLines() para alertar o buffer que ele não
precisa gerar mais linhas.
@Override
public void run() {
buffer.setPendingLines(true);
while (mock.hasMoreLines()){
String line=mock.getLine();
buffer.insert(line);
}
buffer.setPendingLines(false);
}
Programa Exemplo
16. Implemente uma classe chamada Consumer e
especifique que ela implementa a interface Runnable.
public class Consumer implements Runnable {
17. Declare um objeto Buffer e implemente o construtor
da classe que o inicializa.
private Buffer buffer;
public Consumer (Buffer buffer) {
this.buffer=buffer;
}
Programa Exemplo
18. Implemente o método run(). Enquanto o buffer possuir
linhas pendentes, ele tenta recuperar uma e processá-
la.
@Override
public void run() {
while (buffer.hasPendingLines()) {
String line=buffer.get();
processLine(line);
}
}
Programa Exemplo
19. Implemente o método auxiliar processLine(). Ele
apenas espera por até 10 milissegundos para simular
algum tipo de processamento com a linha.
private void processLine(String line) {
try {
Random random=new Random();
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Programa Exemplo
20. Implemente a classe principal do exemplo e adicione o
método main().
public class Main {
public static void main(String[] args) {
21. Crie um objeto FileMock.
FileMock mock=new FileMock(100, 10);
22. Crie um objeto Buffer.
Buffer buffer=new Buffer(20);
23. Crie um objeto Producer e uma Thread para executá-lo.
Producer producer=new Producer(mock,
buffer);
Thread threadProducer=new
Thread(producer,"Producer");
Programa Exemplo
24. Crie três objetos Consumer e três threads para executá-
los.
Consumer consumers[]=new Consumer[3];
Thread threadConsumers[]=new Thread[3];
for (int i=0; i<3; i++){
consumers[i]=new Consumer(buffer);
threadConsumers[i]=new
Thread(consumers[i],"Consumer "+i);
}
Programa Exemplo
25. Inicie o produtor e os três consumidores.
threadProducer.start();
for (int i=0; i<3; i++){
threadConsumers[i].start();
}
Funcionamento
• Todos os objetos Condition estão associados com um
bloqueio e são criados usando o método newCondition()
declarado na interface Lock
• Antes de podermos fazer qualquer operação com uma
condição, temos que ter o controle do bloqueio associado
com a condição, por isso as operações com condições devem
estar em um bloco de código que começa com uma chamada
a um método lock() de um objeto Lock e termina com um
método unlock() do mesmo objeto Lock
• Quando uma thread chama o método await() de uma
condição, ela libera automaticamente o controle do bloqueio,
de modo que alguma outra thread pode obtê-lo e começar a
execução da mesma, ou de outra seção crítica protegida por
esse bloqueio
Funcionamento
• Quando uma thread chama os métodos signal() ou
signallAll() de uma condição, uma ou todas as
threads que estavam esperando por essa condição serão
acordadas, mas isso não garante que a condição que fez
com que elas dormissem seja agora verdadeira, por isso
devemos colocar a chamada await() dentro de um laço
while
• Não podemos deixar esse ciclo até que a condição seja
verdadeira
• Enquanto a condição for falsa, devemos chamar await()
novamente
Funcionamento
• Devemos ter cuidado com o uso de await() e signal():
se chamarmos o método await() em uma condição e
nunca chamarmos o método de signal() nesta
condição, a thread irá dormir para sempre
• Uma thread pode ser interrompida enquanto ele está
dormindo, depois de uma chamada ao método await(),
então temos que processar a exceção
InterruptedException
Usando Múltiplas Condições em um
Bloqueio
• A interface Condition tem outras versões do método
await(), que são os seguintes:
– await(long time, TimeUnit unit): a thread irá dormir até
que:
• Seja interrompida
• Uma outra thread chama os métodos signal() ou signalAll() na
condição
• O tempo especificado passe
• A classe TimeUnit é uma enumeração com as seguintes constantes:
DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES,
NANOSECONDS e SECONDS
– awaitUninterruptibly(): a thread irá dormir até que uma
outra thread chame os métodos signal() ou signalAll(),
que não podem ser interrompidos
Usando Múltiplas Condições em um
Bloqueio
• A interface Condition tem outras versões do método
await(), que são os seguintes (continuação):
– awaitUntil(Date date): a thread ficará dormindo até que:
• Seja interrompida
• Uma outra thread chama os métodos signal() ou signalAll() na
condição
• A data especificada chegue
• Podemos usar condições com os bloqueios ReadLock e
WriteLock de leitura/escrita
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)

More Related Content

What's hot

Memória Interna - Arquitetura e Organização de Computadores
Memória Interna - Arquitetura e Organização de ComputadoresMemória Interna - Arquitetura e Organização de Computadores
Memória Interna - Arquitetura e Organização de ComputadoresWellington Oliveira
 
Node.js - #1 - Introdução - Rodrigo Branas
Node.js - #1 - Introdução - Rodrigo BranasNode.js - #1 - Introdução - Rodrigo Branas
Node.js - #1 - Introdução - Rodrigo BranasRodrigo Branas
 
POO - 02 - Fundamentos da Linguagem Java e da Orientação a Objetos
POO - 02 - Fundamentos da Linguagem Java e da Orientação a ObjetosPOO - 02 - Fundamentos da Linguagem Java e da Orientação a Objetos
POO - 02 - Fundamentos da Linguagem Java e da Orientação a ObjetosLudimila Monjardim Casagrande
 
Ferramenta de apoio a gerência de configuração de software
Ferramenta de apoio a gerência de configuração de softwareFerramenta de apoio a gerência de configuração de software
Ferramenta de apoio a gerência de configuração de softwareelliando dias
 
Análise de Algoritmos - Solução de Recorrências
Análise de Algoritmos - Solução de RecorrênciasAnálise de Algoritmos - Solução de Recorrências
Análise de Algoritmos - Solução de RecorrênciasDelacyr Ferreira
 
Sistemas Operacionais - Aula 4 - Revisão e Exercícios
Sistemas Operacionais - Aula 4 - Revisão e ExercíciosSistemas Operacionais - Aula 4 - Revisão e Exercícios
Sistemas Operacionais - Aula 4 - Revisão e ExercíciosCharles Fortes
 
Apresentação Sistemas Distribuídos - Conceito
Apresentação Sistemas Distribuídos - ConceitoApresentação Sistemas Distribuídos - Conceito
Apresentação Sistemas Distribuídos - ConceitoThiago Marinho
 
Aula 2 - POO: Fundamentos da linguagem Java
Aula 2 - POO: Fundamentos da linguagem JavaAula 2 - POO: Fundamentos da linguagem Java
Aula 2 - POO: Fundamentos da linguagem JavaDaniel Brandão
 
Relógios lógicos
Relógios lógicosRelógios lógicos
Relógios lógicosJunior Asj
 
Apostila de Fundamentos Java
Apostila de Fundamentos JavaApostila de Fundamentos Java
Apostila de Fundamentos JavaMarcio Marinho
 
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software Aula Modelos de Processos Tradicionais para Desenvolvimento de Software
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software Cloves da Rocha
 
Protocolo http
Protocolo httpProtocolo http
Protocolo httpBiel2013a
 
SO-06 Gerenciamento de Memória: Paginação e Segmentação
SO-06 Gerenciamento de Memória: Paginação e SegmentaçãoSO-06 Gerenciamento de Memória: Paginação e Segmentação
SO-06 Gerenciamento de Memória: Paginação e SegmentaçãoEduardo Nicola F. Zagari
 
Sessão do Modelo OSI-Camada 5
Sessão do Modelo OSI-Camada 5Sessão do Modelo OSI-Camada 5
Sessão do Modelo OSI-Camada 5Hélder Batista
 
Sistemas Distribuídos - Aula 05
Sistemas Distribuídos - Aula 05Sistemas Distribuídos - Aula 05
Sistemas Distribuídos - Aula 05Arthur Emanuel
 
Aula 1 sistema operacional linux
Aula 1 sistema operacional linuxAula 1 sistema operacional linux
Aula 1 sistema operacional linuxRogério Cardoso
 
Aula sobre multithreading
Aula sobre multithreadingAula sobre multithreading
Aula sobre multithreadingBianca Dantas
 

What's hot (20)

Memória Interna - Arquitetura e Organização de Computadores
Memória Interna - Arquitetura e Organização de ComputadoresMemória Interna - Arquitetura e Organização de Computadores
Memória Interna - Arquitetura e Organização de Computadores
 
Node.js - #1 - Introdução - Rodrigo Branas
Node.js - #1 - Introdução - Rodrigo BranasNode.js - #1 - Introdução - Rodrigo Branas
Node.js - #1 - Introdução - Rodrigo Branas
 
POO - 02 - Fundamentos da Linguagem Java e da Orientação a Objetos
POO - 02 - Fundamentos da Linguagem Java e da Orientação a ObjetosPOO - 02 - Fundamentos da Linguagem Java e da Orientação a Objetos
POO - 02 - Fundamentos da Linguagem Java e da Orientação a Objetos
 
Ferramenta de apoio a gerência de configuração de software
Ferramenta de apoio a gerência de configuração de softwareFerramenta de apoio a gerência de configuração de software
Ferramenta de apoio a gerência de configuração de software
 
Análise de Algoritmos - Solução de Recorrências
Análise de Algoritmos - Solução de RecorrênciasAnálise de Algoritmos - Solução de Recorrências
Análise de Algoritmos - Solução de Recorrências
 
Sistemas Operacionais - Aula 4 - Revisão e Exercícios
Sistemas Operacionais - Aula 4 - Revisão e ExercíciosSistemas Operacionais - Aula 4 - Revisão e Exercícios
Sistemas Operacionais - Aula 4 - Revisão e Exercícios
 
Apresentação Sistemas Distribuídos - Conceito
Apresentação Sistemas Distribuídos - ConceitoApresentação Sistemas Distribuídos - Conceito
Apresentação Sistemas Distribuídos - Conceito
 
Aula 2 - POO: Fundamentos da linguagem Java
Aula 2 - POO: Fundamentos da linguagem JavaAula 2 - POO: Fundamentos da linguagem Java
Aula 2 - POO: Fundamentos da linguagem Java
 
Processamento paralelo
Processamento paraleloProcessamento paralelo
Processamento paralelo
 
Relógios lógicos
Relógios lógicosRelógios lógicos
Relógios lógicos
 
Apostila de Fundamentos Java
Apostila de Fundamentos JavaApostila de Fundamentos Java
Apostila de Fundamentos Java
 
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software Aula Modelos de Processos Tradicionais para Desenvolvimento de Software
Aula Modelos de Processos Tradicionais para Desenvolvimento de Software
 
SI - Comunicação
SI - ComunicaçãoSI - Comunicação
SI - Comunicação
 
Protocolo http
Protocolo httpProtocolo http
Protocolo http
 
SO-06 Gerenciamento de Memória: Paginação e Segmentação
SO-06 Gerenciamento de Memória: Paginação e SegmentaçãoSO-06 Gerenciamento de Memória: Paginação e Segmentação
SO-06 Gerenciamento de Memória: Paginação e Segmentação
 
Sessão do Modelo OSI-Camada 5
Sessão do Modelo OSI-Camada 5Sessão do Modelo OSI-Camada 5
Sessão do Modelo OSI-Camada 5
 
Sistemas Distribuídos - Aula 05
Sistemas Distribuídos - Aula 05Sistemas Distribuídos - Aula 05
Sistemas Distribuídos - Aula 05
 
Processos e threads cap 02 (i unidade)
Processos e threads   cap 02 (i unidade)Processos e threads   cap 02 (i unidade)
Processos e threads cap 02 (i unidade)
 
Aula 1 sistema operacional linux
Aula 1 sistema operacional linuxAula 1 sistema operacional linux
Aula 1 sistema operacional linux
 
Aula sobre multithreading
Aula sobre multithreadingAula sobre multithreading
Aula sobre multithreading
 

Viewers also liked

Programação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IProgramação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IFabio Moura Pereira
 
Programação Concorrente - Gerenciamento de Threads - Parte II
Programação Concorrente - Gerenciamento de Threads - Parte IIProgramação Concorrente - Gerenciamento de Threads - Parte II
Programação Concorrente - Gerenciamento de Threads - Parte IIFabio Moura Pereira
 
04 - Gerenciamento de Threads - II
04 -  Gerenciamento de Threads - II04 -  Gerenciamento de Threads - II
04 - Gerenciamento de Threads - IIFabio Moura Pereira
 
Desenvolvimento de Sistemas Web - HTML5 - Introdução
Desenvolvimento de Sistemas Web - HTML5 - IntroduçãoDesenvolvimento de Sistemas Web - HTML5 - Introdução
Desenvolvimento de Sistemas Web - HTML5 - IntroduçãoFabio Moura Pereira
 
Aula de Desenvolvimento de Sistemas Web - CSS3
Aula de Desenvolvimento de Sistemas Web - CSS3Aula de Desenvolvimento de Sistemas Web - CSS3
Aula de Desenvolvimento de Sistemas Web - CSS3Fabio Moura Pereira
 
Curso de Desenvolvimento de Sistemas Web - (X)HTML
Curso de Desenvolvimento de Sistemas Web - (X)HTMLCurso de Desenvolvimento de Sistemas Web - (X)HTML
Curso de Desenvolvimento de Sistemas Web - (X)HTMLFabio Moura Pereira
 
Programação Concorrente - Introdução
Programação Concorrente - IntroduçãoProgramação Concorrente - Introdução
Programação Concorrente - IntroduçãoFabio Moura Pereira
 
Aula Interface Gráfica do Usuário
Aula Interface Gráfica do UsuárioAula Interface Gráfica do Usuário
Aula Interface Gráfica do UsuárioFabio Moura Pereira
 
Desenvolvimento de Sistemas Web - Conceitos Básicos
Desenvolvimento de Sistemas Web - Conceitos BásicosDesenvolvimento de Sistemas Web - Conceitos Básicos
Desenvolvimento de Sistemas Web - Conceitos BásicosFabio Moura Pereira
 
Aula de Prolog 07 - Estruturas de Dados
Aula de Prolog 07 - Estruturas de DadosAula de Prolog 07 - Estruturas de Dados
Aula de Prolog 07 - Estruturas de DadosFabio Moura Pereira
 
Aula de Prolog 08 - Unificação
Aula de Prolog 08 - UnificaçãoAula de Prolog 08 - Unificação
Aula de Prolog 08 - UnificaçãoFabio Moura Pereira
 

Viewers also liked (20)

Programação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IProgramação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte I
 
Programação Concorrente - Gerenciamento de Threads - Parte II
Programação Concorrente - Gerenciamento de Threads - Parte IIProgramação Concorrente - Gerenciamento de Threads - Parte II
Programação Concorrente - Gerenciamento de Threads - Parte II
 
Aula Prolog - 05
Aula Prolog - 05Aula Prolog - 05
Aula Prolog - 05
 
04 - Gerenciamento de Threads - II
04 -  Gerenciamento de Threads - II04 -  Gerenciamento de Threads - II
04 - Gerenciamento de Threads - II
 
Desenvolvimento de Sistemas Web - HTML5 - Introdução
Desenvolvimento de Sistemas Web - HTML5 - IntroduçãoDesenvolvimento de Sistemas Web - HTML5 - Introdução
Desenvolvimento de Sistemas Web - HTML5 - Introdução
 
Haskell - Introdução
Haskell - IntroduçãoHaskell - Introdução
Haskell - Introdução
 
Aula de Desenvolvimento de Sistemas Web - CSS3
Aula de Desenvolvimento de Sistemas Web - CSS3Aula de Desenvolvimento de Sistemas Web - CSS3
Aula de Desenvolvimento de Sistemas Web - CSS3
 
Aula Tratamento de Exceções
Aula Tratamento de ExceçõesAula Tratamento de Exceções
Aula Tratamento de Exceções
 
Aula 05 - Java Script Básico
Aula 05 -  Java Script BásicoAula 05 -  Java Script Básico
Aula 05 - Java Script Básico
 
Curso de Desenvolvimento de Sistemas Web - (X)HTML
Curso de Desenvolvimento de Sistemas Web - (X)HTMLCurso de Desenvolvimento de Sistemas Web - (X)HTML
Curso de Desenvolvimento de Sistemas Web - (X)HTML
 
Aula Prolog 03
Aula Prolog 03Aula Prolog 03
Aula Prolog 03
 
Aula Persistência 01 (Java)
Aula Persistência 01 (Java)Aula Persistência 01 (Java)
Aula Persistência 01 (Java)
 
Programação Concorrente - Introdução
Programação Concorrente - IntroduçãoProgramação Concorrente - Introdução
Programação Concorrente - Introdução
 
Aula Interface Gráfica do Usuário
Aula Interface Gráfica do UsuárioAula Interface Gráfica do Usuário
Aula Interface Gráfica do Usuário
 
Desenvolvimento de Sistemas Web - Conceitos Básicos
Desenvolvimento de Sistemas Web - Conceitos BásicosDesenvolvimento de Sistemas Web - Conceitos Básicos
Desenvolvimento de Sistemas Web - Conceitos Básicos
 
Aula Java Swing
Aula Java SwingAula Java Swing
Aula Java Swing
 
Curso de PHP - Objetos
Curso de PHP - ObjetosCurso de PHP - Objetos
Curso de PHP - Objetos
 
Aula de Prolog 07 - Estruturas de Dados
Aula de Prolog 07 - Estruturas de DadosAula de Prolog 07 - Estruturas de Dados
Aula de Prolog 07 - Estruturas de Dados
 
Aula de Prolog 08 - Unificação
Aula de Prolog 08 - UnificaçãoAula de Prolog 08 - Unificação
Aula de Prolog 08 - Unificação
 
Prolog 04 - Regras
Prolog 04 - RegrasProlog 04 - Regras
Prolog 04 - Regras
 

Similar to 05 - Sincronização de Threads - I

Programação Web com Zend Framework e Ajax com Dojo
Programação Web com Zend Framework e Ajax com DojoProgramação Web com Zend Framework e Ajax com Dojo
Programação Web com Zend Framework e Ajax com Dojofabioginzel
 
Trabalhando com Threads em JAVA
Trabalhando com Threads em JAVATrabalhando com Threads em JAVA
Trabalhando com Threads em JAVAMarcio Palheta
 
Apostila: Curso de java III
Apostila: Curso de java IIIApostila: Curso de java III
Apostila: Curso de java IIIVerônica Veiga
 
Trabalho ProgramaçãO Comercial Ii
Trabalho ProgramaçãO Comercial IiTrabalho ProgramaçãO Comercial Ii
Trabalho ProgramaçãO Comercial IiMateus Ramos Pereira
 
Aprendendo C# do zero
Aprendendo C# do zeroAprendendo C# do zero
Aprendendo C# do zeroManawydan
 
1 2 3 - Testando - Automatizando os testes de software
1 2 3 - Testando - Automatizando os testes de software1 2 3 - Testando - Automatizando os testes de software
1 2 3 - Testando - Automatizando os testes de softwareHeider Lopes
 
Spring & Struts
Spring & StrutsSpring & Struts
Spring & Strutseduan
 
Mvc model view controller - java para desenvolvimento web
Mvc   model view controller - java para desenvolvimento webMvc   model view controller - java para desenvolvimento web
Mvc model view controller - java para desenvolvimento webValdir Junior
 
Php5 Orientado A Objetos
Php5 Orientado A ObjetosPhp5 Orientado A Objetos
Php5 Orientado A ObjetosRápido Site
 
Teste de Integração - Unidade III
Teste de Integração - Unidade IIITeste de Integração - Unidade III
Teste de Integração - Unidade IIIJoão Lourenço
 
Java memory model primary ref. - faq
Java memory model   primary ref. - faqJava memory model   primary ref. - faq
Java memory model primary ref. - faqPedro De Almeida
 
Java 08 Modificadores Acesso E Membros De Classe
Java 08 Modificadores Acesso E Membros De ClasseJava 08 Modificadores Acesso E Membros De Classe
Java 08 Modificadores Acesso E Membros De ClasseRegis Magalhães
 
Desenvolvimento de sistemas web com php Frameworks - Aula 1
Desenvolvimento de sistemas web com php Frameworks - Aula 1Desenvolvimento de sistemas web com php Frameworks - Aula 1
Desenvolvimento de sistemas web com php Frameworks - Aula 1Thyago Maia
 

Similar to 05 - Sincronização de Threads - I (20)

Programação Web com Zend Framework e Ajax com Dojo
Programação Web com Zend Framework e Ajax com DojoProgramação Web com Zend Framework e Ajax com Dojo
Programação Web com Zend Framework e Ajax com Dojo
 
Trabalhando com Threads em JAVA
Trabalhando com Threads em JAVATrabalhando com Threads em JAVA
Trabalhando com Threads em JAVA
 
Tutorial Java: Interface
Tutorial Java: InterfaceTutorial Java: Interface
Tutorial Java: Interface
 
Apostila: Curso de java III
Apostila: Curso de java IIIApostila: Curso de java III
Apostila: Curso de java III
 
Trabalho ProgramaçãO Comercial Ii
Trabalho ProgramaçãO Comercial IiTrabalho ProgramaçãO Comercial Ii
Trabalho ProgramaçãO Comercial Ii
 
CURSO JAVA 01
CURSO JAVA 01CURSO JAVA 01
CURSO JAVA 01
 
Aprendendo C# do zero
Aprendendo C# do zeroAprendendo C# do zero
Aprendendo C# do zero
 
1 2 3 - Testando - Automatizando os testes de software
1 2 3 - Testando - Automatizando os testes de software1 2 3 - Testando - Automatizando os testes de software
1 2 3 - Testando - Automatizando os testes de software
 
Spring & Struts
Spring & StrutsSpring & Struts
Spring & Struts
 
Curso de ReactJS
Curso de ReactJSCurso de ReactJS
Curso de ReactJS
 
Java13
Java13Java13
Java13
 
Mvc model view controller - java para desenvolvimento web
Mvc   model view controller - java para desenvolvimento webMvc   model view controller - java para desenvolvimento web
Mvc model view controller - java para desenvolvimento web
 
Aula1
Aula1Aula1
Aula1
 
Php5 Orientado A Objetos
Php5 Orientado A ObjetosPhp5 Orientado A Objetos
Php5 Orientado A Objetos
 
Teste de Integração - Unidade III
Teste de Integração - Unidade IIITeste de Integração - Unidade III
Teste de Integração - Unidade III
 
Mvc delphi
Mvc delphiMvc delphi
Mvc delphi
 
Java memory model primary ref. - faq
Java memory model   primary ref. - faqJava memory model   primary ref. - faq
Java memory model primary ref. - faq
 
Java 08 Modificadores Acesso E Membros De Classe
Java 08 Modificadores Acesso E Membros De ClasseJava 08 Modificadores Acesso E Membros De Classe
Java 08 Modificadores Acesso E Membros De Classe
 
Conhecendo o Spring
Conhecendo o SpringConhecendo o Spring
Conhecendo o Spring
 
Desenvolvimento de sistemas web com php Frameworks - Aula 1
Desenvolvimento de sistemas web com php Frameworks - Aula 1Desenvolvimento de sistemas web com php Frameworks - Aula 1
Desenvolvimento de sistemas web com php Frameworks - Aula 1
 

More from Fabio Moura Pereira

More from Fabio Moura Pereira (15)

Aula Prolog 09 - Listas
Aula Prolog 09 - ListasAula Prolog 09 - Listas
Aula Prolog 09 - Listas
 
Aula de Prolog 06 - Recursão
Aula de Prolog 06 - RecursãoAula de Prolog 06 - Recursão
Aula de Prolog 06 - Recursão
 
Aula Prolog 02
Aula Prolog 02Aula Prolog 02
Aula Prolog 02
 
Aula Prolog 01
Aula Prolog 01Aula Prolog 01
Aula Prolog 01
 
Programação Concorrente - Objetos e Concorrência
Programação Concorrente - Objetos e ConcorrênciaProgramação Concorrente - Objetos e Concorrência
Programação Concorrente - Objetos e Concorrência
 
Aula - Interfaces e Estilos de Interação
Aula - Interfaces e Estilos de InteraçãoAula - Interfaces e Estilos de Interação
Aula - Interfaces e Estilos de Interação
 
Aula Desenvolvimento de Jogos - Game Engine (Motor de Jogos)
Aula Desenvolvimento de Jogos - Game Engine (Motor de Jogos)Aula Desenvolvimento de Jogos - Game Engine (Motor de Jogos)
Aula Desenvolvimento de Jogos - Game Engine (Motor de Jogos)
 
Padrões de Projeto de Software
Padrões de Projeto de SoftwarePadrões de Projeto de Software
Padrões de Projeto de Software
 
Curso de PHP - Arrays
Curso de PHP - ArraysCurso de PHP - Arrays
Curso de PHP - Arrays
 
Desenvolvimento de Jogos - Game Design
Desenvolvimento de Jogos - Game DesignDesenvolvimento de Jogos - Game Design
Desenvolvimento de Jogos - Game Design
 
Desenvolvimento de Jogos - Mercado Parte 2
Desenvolvimento de Jogos - Mercado Parte 2Desenvolvimento de Jogos - Mercado Parte 2
Desenvolvimento de Jogos - Mercado Parte 2
 
Desenvolvimento de Jogos - Mercado Parte 1
Desenvolvimento de Jogos - Mercado Parte 1Desenvolvimento de Jogos - Mercado Parte 1
Desenvolvimento de Jogos - Mercado Parte 1
 
PHP - Funções
PHP - FunçõesPHP - Funções
PHP - Funções
 
PHP - Introdução
PHP - IntroduçãoPHP - Introdução
PHP - Introdução
 
PHP - Strings
PHP - StringsPHP - Strings
PHP - Strings
 

05 - Sincronização de Threads - 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 • Sincronizando um Método • Atributos Independentes em Classes Sincronizadas • Usando Condições em Código Sincronizado • Sincronizando um Bloco de Código com um Bloqueio • Sincronizando Acesso a Dados com Bloqueios de Leitura/Escrita • Modificando o Equilíbrio entre Bloqueios • Usando Múltiplas Condições em um Bloqueio
  • 3.
  • 4. Introdução • Uma das situações mais comuns em programação concorrente ocorre quando mais de uma thread de execução compartilha um recurso • Em um aplicativo concorrente, é normal que várias threads leiam ou escrevam os mesmos dados ou tenham acesso ao mesmo arquivo ou conexão com o banco de dados • Esses recursos compartilhados podem provocar situações de erro ou inconsistência de dados e temos que implementar mecanismos para evitar esses erros • A solução para estes problemas é fornecida com o conceito de seção crítica • A seção crítica é um bloco de código que acessa um recurso compartilhado e não pode ser executado por mais de uma thread, ao mesmo tempo.
  • 5. Introdução • Para ajudar os programadores a implementar seções críticas, Java (e quase todas as linguagens de programação) oferece mecanismos de sincronização • Quando uma thread quer acesso a uma seção crítica, ela usa um desses mecanismos de sincronização para saber se há alguma outra thread em execução na seção crítica • Se não há, a thread entra na seção crítica, caso contrário, a thread é suspensa pelo mecanismo de sincronização até que a thread que está executando a seção crítica termine • Quando mais de uma thread está aguardando uma outra thread terminar a execução de uma seção crítica, a JVM escolhe uma delas, e o restante esperar pela sua vez
  • 6. Introdução • Esta aula apresenta uma série de exemplos que mostram como usar os dois mecanismos de sincronização básicos oferecidos pela linguagem Java: – A palavra-chave synchronized – A interface Lock e suas implementações
  • 7.
  • 8. Sincronizando um Método • Veremos como usar um dos métodos mais básicos para a sincronização em Java, isto é, o uso da palavra-chave synchronized para controlar o acesso simultâneo a um método • Apenas uma thread de execução irá acessar um dos métodos de um objeto declarado com a palavra-chave synchronized • Se outra thread tenta acessar qualquer método declarado com a palavra-chave synchronized do mesmo objeto, ele será suspenso até que a primeira thread termine a execução do método • Em outras palavras, cada método declarado com a palavra- chave synchronized é uma seção crítica e Java só permite a execução de uma das seções críticas de um objeto
  • 9. Sincronizando um Método • Os métodos estáticos possuem um comportamento diferente: apenas uma thread de execução irá acessar um dos métodos estáticos declarados com a palavra-chave synchronized, mas outra thread pode acessar outros métodos não-estáticos de um objeto dessa classe • Temos que ter muito cuidado com este ponto, porque duas threads podem acessar dois métodos synchronized diferentes se um é estático e o outro não é • Se ambos os métodos alterar os mesmos dados, podemos ter erros de inconsistência de dados
  • 10. Sincronizando um Método • Para aprender este conceito, vamos implementar um exemplo com duas threads acessando um objeto comum • Vamos ter uma conta bancária e duas threads: uma que transfere dinheiro para a conta e outra que retira dinheiro da conta – Sem métodos de sincronização, poderíamos ter resultados incorretos – Mecanismos de sincronização garantem que o saldo final da conta esteja correto
  • 11. Programa Exemplo 1. Crie uma classe chamada Account que irá modelar nossa conta bancária. Ela possui um único atributo double, chamado balance (saldo). public class Account { private double balance; 2. Implemente os métodos setBalance() e getBalance() para escrever e ler o valor do atributo. public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; }
  • 12. Programa Exemplo 3. Implementar um método chamado addAmount() que aumenta o valor do saldo em uma certa quantidade que é passada para o método. Apenas uma thread deve alterar o valor do saldo, portanto, use a palavra-chave synchronized para converter este método em uma seção crítica. public synchronized void addAmount(double amount) { double tmp=balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp+=amount; balance=tmp; }
  • 13. Programa Exemplo 4. Implementar um método chamado subtractAmount() que diminui o valor do saldo em uma certa quantidade que é passada para o método. Apenas uma thread deve alterar o valor do saldo, portanto, use a palavra-chave synchronized para converter este método em uma seção crítica. public synchronized void subtractAmount(double amount) { double tmp=balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp-=amount; balance=tmp; }
  • 14. Programa Exemplo 5. Implemente uma classe que simula um caixa eletrônico (ATM). Ele vai usar o método subtractAmount() para diminuir o saldo de uma conta. Esta classe deve implementar a interface Runnable a ser executada como uma thread. public class Bank implements Runnable { 6. Adicione um objeto Account a esta classe. Implemente o construtor da classe que inicializa o objeto Account. private Account account; public Bank(Account account) { this.account=account; }
  • 15. Programa Exemplo 7. Implemente o método run(). Ele faz 100 chamadas ao método subtractAmount() de uma conta para reduzir o saldo. @Override public void run() { for (int i=0; i<100; i++){ account.subtractAmount(1000); } } 8. Implementar uma classe que simula uma empresa e utiliza o método addAmount() da classe Account para incrementar o saldo da conta. Esta classe deve implementar a interface Runnable a ser executada como uma thread. public class Company implements Runnable {
  • 16. Programa Exemplo 9. Adicione um objeto Account a esta classe. Implemente o construtor da classe que inicializa o objeto account. private Account account; public Company(Account account) { this.account=account; } 10. Implemente o método run(). Ele faz 100 chamadas ao método addAmount() de uma conta para incrementar o saldo. @Override public void run() { for (int i=0; i<100; i++){ account.addAmount(1000); } }
  • 17. Programa Exemplo 11. Implementar a classe principal do aplicativo criando uma classe principal que contém o método main(). public class Main { public static void main(String[] args) { 12. Crie um objeto Account e inicialize o seu saldo com 1000. Account account=new Account(); account.setBalance(1000); 13. Crie um objeto Company e uma Thread para executá-lo. Company company=new Company(account); Thread companyThread=new Thread(company);
  • 18. Programa Exemplo 14. Crie um objeto Bank e uma Thread para executá-lo. Bank bank=new Bank(account); Thread bankThread=new Thread(bank); 15. Escreva o saldo inicial no console. Inicie as threads. System.out.printf("Conta : Saldo Inicial: %fn",account.getBalance()); companyThread.start(); bankThread.start();
  • 19. Programa Exemplo 16. Aguarde a finalização das duas threads usando o método join() e imprima no console o saldo final da conta. try { companyThread.join(); bankThread.join(); System.out.printf("Conta : Saldo Final: %fn",account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); }
  • 20. Funcionamento • Neste exemplo, desenvolvemos um aplicativo que incrementa e diminui o saldo de uma classe que simula uma conta bancária • O programa faz 100 chamadas ao método addAmount() que incrementa o saldo em 1.000 em cada chamada e 100 chamadas ao método subtractAmount() que diminui o saldo em 1.000 em cada chamada • Devemos esperar que os saldos inicial e final sejam iguais
  • 21. Funcionamento • Tentamos forçar uma situação de erro usando uma variável chamada tmp para armazenar o valor do saldo da conta, de modo que lemos o saldo da conta, incrementamos o valor da variável temporária e então estabelecemos o valor do saldo da conta novamente • Além disso, introduzimos um pouco de atraso, utilizando o método sleep() da classe Thread para colocar a thread que está executando o método para dormir por 10 milissegundos, portanto, se outra thread executar esse método, ela pode modificar o equilíbrio da conta, provocando um erro • É o mecanismo da palavra-chave synchronized que evita esses erros
  • 22. Funcionamento • Se quisermos ver os problemas de acesso concorrente aos dados compartilhados, basta apagarmos a palavra- chave synchronized dos métodos addAmount() e subtractAmount() e executarmos o programa • Sem a palavra-chave synchronized, enquanto uma thread está dormindo após a leitura do valor do saldo da conta, outro método irá ler o saldo da conta, então ambos os métodos irão modificar o mesmo saldo e uma das operações não será refletida no resultado final
  • 23. Funcionamento • Como podemos ver na captura de tela seguinte, podemos obter resultados inconsistentes:
  • 24. Funcionamento • Se executarmos o programa várias vezes, obteremos resultados diferentes • A ordem de execução das threads não é garantida pela JVM, então, toda vez que executarmos o programa, as threads vão ler e modificar o saldo da conta em uma ordem diferente, de modo que o resultado final será diferente • Agora, adicione a palavra-chave synchronized como vimos antes e execute o programa novamente
  • 25. Funcionamento • Como podemos ver na imagem seguinte, agora obtemos o resultado esperado • Se você executar o programa várias vezes, iremos obter o mesmo resultado • Veja a imagem a seguir:
  • 26. Funcionamento • Usando a palavra-chave synchronized, nós garantimos o acesso correto para os dados compartilhados em aplicações concorrentes • Como mencionado na introdução, apenas uma thread pode acessar os métodos de um objeto que use a palavra-chave synchronized na sua declaração • Se uma thread (A) está executando um método synchronized e outra thread (B), quer executar outro métodos synchronized do mesmo objeto, ela vai ser bloqueada até que a thread (A) termine • Mas se threadB tem acesso a diferentes objetos da mesma classe, nenhum deles será bloqueado
  • 27. Sincronizando um Método • A palavra-chave synchronized penaliza o desempenho da aplicação, portanto, devemos usá-la apenas sobre os métodos que modificam os dados compartilhados em um ambiente concorrente • Se temos várias threads que chamam um método synchronized, apenas um irá executá-los em um momento enquanto os outros vão estar à espera • Se essa operação não utilizar a palavra-chave synchronized, todos as threads podem executar a operação, ao mesmo tempo, reduzindo o tempo total de execução • Se sabemos que um método não será chamado por mais de uma thread, não devemos usar a palavra-chave synchronized
  • 28. Sincronizando um Método • Podemos usar chamadas recursivas com métodos synchronized • À medida que a thread tem acesso aos métodos synchronized de um objeto, podemos chamar outros métodos synchronized desse objeto, incluindo o método que está sendo executado, ela não terá que conseguir acesso aos métodos synchronized novamente
  • 29. Sincronizando um Método • Podemos usar a palavra-chave synchronized para proteger o acesso a um bloco de código em vez de um método inteiro • Devemos utilizar a palavra-chave synchronized desta forma para proteger o acesso aos dados compartilhados, deixando o restante das operações de fora do bloco, obtendo-se um melhor desempenho da aplicação synchronized (this) { // Java code }
  • 30. Sincronizando um Método • O objetivo é fazer com que a seção crítica (o bloco de código que só pode ser acessado por uma thread por vez) seja tão curta quanto possível • Temos usado a palavra-chave synchronized para proteger o acesso à instrução que atualiza o número de pessoas no prédio, deixando de fora as longas operações deste bloco que não usam os dados compartilhados • Quando usamos a palavra-chave synchronized dessa maneira, devemos passar uma referência de objeto como um parâmetro • Apenas uma thread pode acessar o código synchronized (blocos ou métodos) desse objeto • Normalmente, vamos usar a palavra-chave this para referenciar o objeto que está executando o método
  • 31.
  • 32. Atributos Independentes em Classes Sincronizadas • Quando usamos a palavra-chave synchronized para proteger um bloco de código, devemos passar uma referência de objeto como um parâmetro • Normalmente, iremos usar a palavra-chave this para referenciar o objeto que executa o método, mas podemos usar outras referências de objeto • Normalmente, esses objetos serão criados exclusivamente com esta finalidade • Por exemplo, se temos dois atributos independentes em uma classe compartilhada por várias threads, devemos sincronizar o acesso a cada variável, mas não há nenhum problema se houver uma thread acessando um dos atributos e outra thread acessando o outro, ao mesmo tempo
  • 33. Atributos Independentes em Classes Sincronizadas • Neste exemplo, iremos aprender como resolver esta situação com um programa que simula um cinema com duas telas e duas bilheterias • Quando uma bilheteria vende bilhetes, eles são para um dos dois filmes, mas não para ambos, então o número de assentos livres em cada sala são atributos independentes
  • 34. Programa Exemplo 1. Crie uma classe chamada Cinema e adicione dois atributos long chamados vacanciesCinema1 e vacanciesCinema2. public class Cinema { private long vacanciesCinema1; private long vacanciesCinema2; 2. Adicione à classe Cinema dois atributos Object adicionais chamados controlCinema1 e controlCinema2. private final Object controlCinema1, controlCinema2;
  • 35. Programa Exemplo 3. Implemente o construtor da classe Cinema que inicializa todos os atributos da classe. public Cinema(){ controlCinema1=new Object(); controlCinema2=new Object(); vacanciesCinema1=20; vacanciesCinema2=20; }
  • 36. Programa Exemplo 4. Implemente o método sellTickets1() que é chamado quando alguns bilhetes para o primeiro cinema são vendidos. Ele utiliza o objeto controlCinema1 para controlar o acesso ao bloco de código synchronized. public boolean sellTickets1 (int number) { synchronized (controlCinema1) { if (number<vacanciesCinema1) { vacanciesCinema1-=number; return true; } else { return false; } } }
  • 37. Programa Exemplo 5. Implemente o método sellTickets2() que é chamado quando alguns bilhetes para o segundo cinema são vendidos. Ele utiliza o objeto controlCinema2 para controlar o acesso ao bloco de código synchronized. public boolean sellTickets2 (int number) { synchronized (controlCinema2) { if (number<vacanciesCinema2) { vacanciesCinema2-=number; return true; } else { return false; } } }
  • 38. Programa Exemplo 6. Implemente o método returnTickets1() que é chamado quando alguns bilhetes para o primeiro cinema são devolvidos. Ele utiliza o objeto controlCinema1 para controlar o acesso ao bloco de código synchronized. public boolean returnTickets1 (int number) { synchronized (controlCinema1) { vacanciesCinema1+=number; return true; } }
  • 39. Programa Exemplo 7. Implemente o método returnTickets2() que é chamado quando alguns bilhetes para o segundo cinema são devolvidos. Ele utiliza o objeto controlCinema2 para controlar o acesso ao bloco de código synchronized. public boolean returnTickets2 (int number) { synchronized (controlCinema2) { vacanciesCinema2+=number; return true; } }
  • 40. Programa Exemplo 8. Implemente outros dois métodos que retornam o número de vagas em cada cinema. public long getVacanciesCinema1() { return vacanciesCinema1; } public long getVacanciesCinema2() { return vacanciesCinema2; } 9. Implemente a classe TicketOffice1 e especifique que ela implementa a interface Runnable. public class TicketOffice1 implements Runnable {
  • 41. Programa Exemplo 10. Declare um objeto Cinema e implemente o construtor da classe que inicializa este objeto. private Cinema cinema; public TicketOffice1 (Cinema cinema) { this.cinema=cinema; }
  • 42. Programa Exemplo 11. Implemente o método run() que simula algumas operações sobre os dois cinemas. @Override public void run() { cinema.sellTickets1(3); cinema.sellTickets1(2); cinema.sellTickets2(2); cinema.returnTickets1(3); cinema.sellTickets1(5); cinema.sellTickets2(2); cinema.sellTickets2(2); cinema.sellTickets2(2); }
  • 43. Programa Exemplo 12. Implemente a classe TicketOffice2 e especifique que ela implementa a interface Runnable. public class TicketOffice2 implements Runnable { 13. Declare um objeto Cinema e implemente o construtor da classe que inicializa este objeto. private Cinema cinema; public TicketOffice2 (Cinema cinema) { this.cinema=cinema; }
  • 44. Programa Exemplo 14. Implemente o método run() que simula algumas operações sobre os dois cinemas. @Override public void run() { cinema.sellTickets2(2); cinema.sellTickets2(4); cinema.sellTickets1(2); cinema.sellTickets1(1); cinema.returnTickets2(2); cinema.sellTickets1(3); cinema.sellTickets2(2); cinema.sellTickets1(2); }
  • 45. Programa Exemplo 15. Implemente a classe principal do exemplo através da criação de uma classe principal e adicionando-lhe o método main(). public class Main { public static void main(String[] args) { 16. Declare e crie um objeto Cinema. Cinema cinema=new Cinema(); 17. Crie um objeto TicketOffice1 e a Thread para executá-lo. TicketOffice1 ticketOffice1=new TicketOffice1(cinema); Thread thread1=new Thread(ticketOffice1, "TicketOffice1");
  • 46. Programa Exemplo 18. Crie um objeto TicketOffice2 e a Thread para executá-lo. TicketOffice2 ticketOffice2=new TicketOffice2(cinema); Thread thread2=new Thread(ticketOffice2, "TicketOffice2"); 19. Inicie ambas as threads. thread1.start(); thread2.start();
  • 47. Programa Exemplo 20. Aguarde até que as threads sejam finalizadas. try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } 21. Escreva no console as vagas disponíveis nos dois cinemas. System.out.printf("Sala 1 Vagas: %dn", cinema.getVacanciesCinema1()); System.out.printf("Sala 2 Vagas: %dn", cinema.getVacanciesCinema2());
  • 48. Funcionamento • Quando usamos a palavra-chave synchronized para proteger um bloco de código, podemos usar um objeto como um parâmetro • A JVM garante que apenas uma thread pode ter acesso a todos os blocos de código protegidas com esse objeto (note que sempre falamos sobre objetos, e não sobre classes)
  • 49. Funcionamento • Neste exemplo, temos um objeto que controla o acesso ao atributo vacanciesCinema1, portanto, apenas uma thread pode modificar este atributo de cada vez, e outro objeto que controla o acesso ao atributo vacanciesCinema2, portanto, apenas uma thread pode modificar este atributo de cada vez • Mas podem haver duas threads em execução concorrente, uma modificando o atributo vacancesCinema1 e a outra modificando o atributo vacanciesCinema2
  • 50. Funcionamento • Quando executarmos esse exemplo, podemos ver como o resultado final é sempre o número esperado de vagas para cada cinema • Na captura de tela seguinte, podemos ver os resultados de uma execução da aplicação:
  • 51.
  • 52. Usando Condições em Código Sincronizado • Um problema clássico na programação concorrente é o problema do produtor-consumidor • Temos um buffer de dados, um ou mais produtores de dados que os salvam no buffer e um ou mais consumidores de dados que leem a partir do buffer • À medida que o buffer é uma estrutura de dados compartilhada, temos que controlar o acesso a ela através de um mecanismo de sincronização como o da palavra-chave synchronized, mas que tenha mais limitações • Um produtor não pode salvar dados no buffer se ele está cheio e que o consumidor não pode retirar dados do buffer se ele está vazio
  • 53. Usando Condições em Código Sincronizado • Para esses tipos de situações, Java fornece os métodos wait(), notify() e notifyAll() implementado na classe Object • Uma thread pode chamar o método wait() dentro de um bloco de código synchronized – Se ela chamar o método wait() do lado de fora de um bloco de código synchronized, a JVM lança uma exceção IllegalMonitorStateException • Quando a thread chama o método wait(), a JVM coloca a thread para dormir e libera o objeto que controla o bloco de código synchronized que está em execução e permite que as outras threads executem outros blocos de código synchronized protegidos por esse objeto • Para acordar a thread, devemos chamar o método notify() ou notifyAll() dentro de um bloco de código protegido pelo mesmo objeto
  • 54. Usando Condições em Código Sincronizado • Neste exemplo, vamos aprender como implementar o problema do produtor-consumidor, utilizando a palavra- chave synchronized e os métodos wait(), notify(), e notifyAll()
  • 55. Programa Exemplo 1. Crie uma classe chamada EventStorage. Ela tem dois atributos: um atributo int chamado maxSize e um atributo LinkedList <Date> chamado storage. public class EventStorage { private int maxSize; private LinkedList<Date> storage; 2. Implemente o construtor da classe que inicializa os seus atributos. public EventStorage(){ maxSize=10; storage=new LinkedList<>(); }
  • 56. Programa Exemplo 3. Implemente o método synchronized set() para armazenar um evento. Primeiro, verifique se o buffer está cheio ou não. Se ele estiver cheio, chame o método wait() até que tenha espaço vazio. No final do método, chamamos o método notifyAll() para acordar todos as threads que estão dormindo no método wait(). public synchronized void set(){ while (storage.size()==maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.offer(new Date()); System.out.printf("Set: %dn",storage.size()); notifyAll(); }
  • 57. Programa Exemplo 4. Implementar o método synchronized get() para obter um evento do armazenamento. Primeiro, verifique se o buffer tem eventos ou não. Se ele não tem eventos, chame o método wait() até que tenha algum evento. No final do método, chamamos o método notifyAll() para acordar todas as threads que estão dormindo no método wait(). public synchronized void get(){ while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Get: %d: %sn",storage.size(), ((LinkedList<?>)storage).poll()); notifyAll(); }
  • 58. Programa Exemplo 5. Crie uma classe chamada Producer e especifique que ela implementa a interface Runnable. Ela irá implementar o produtor do exemplo. public class Producer implements Runnable { 6. Declare um objeto EventStorage e implemente o construtor da classe, que inicializa este objeto. private EventStorage storage; public Producer(EventStorage storage){ this.storage=storage; }
  • 59. Programa Exemplo 7. Implemente o método run() que chama 100 vezes o método set() do objeto EventStorage. @Override public void run() { for (int i=0; i<100; i++){ storage.set(); } } 8. Crie uma classe chamada Consumer e especifique que ela implementa a interface Runnable. Ela irá implementar o consumidor para o exemplo. public class Consumer implements Runnable {
  • 60. Programa Exemplo 9. Declare um objeto EventStorage e implemente o construtor da classe que inicializa este objeto. private EventStorage storage; public Consumer(EventStorage storage){ this.storage=storage; } 10. Implemente o método run(). Ele chama 100 vezes o método get() do objeto EventStorage. @Override public void run() { for (int i=0; i<100; i++){ storage.get(); } }
  • 61. Programa Exemplo 11. Crie a classe principal para o exemplo e adicione o método main(). public class Main { public static void main(String[] args) { 12. Crie um objeto EventStorage. EventStorage storage=new EventStorage(); 13. Crie um objeto Producer e a Thread para executá-lo. Producer producer=new Producer(storage); Thread thread1=new Thread(producer);
  • 62. Programa Exemplo 14. Crie um objeto Consumer e a Thread para executá-lo. Consumer consumer=new Consumer(storage); Thread thread2=new Thread(consumer); 15. Inicie ambas as threads. thread2.start(); thread1.start();
  • 63. Funcionamento • A chave para este exemplo são os métodos set() e get() da classe EventStorage • Primeiro, o método set() verifica se há espaço livre no atributo de armazenamento – Se ele está cheio, ele chama o método wait() para aguardar espaço livre • Quando a outra thread chama o método notifyAll(), a thread acorda e verifica a condição novamente – O método notifyAll() não garante que a thread vai acordar • Este processo é repetido até que haja espaço livre no armazenamento, então ela pode gerar um novo evento e armazená-lo
  • 64. Funcionamento • O comportamento do método get() é semelhante, primeiro, ele verifica se há eventos no armazenamento – Se a classe EventStorage está vazia, ele chama o método wait() para aguardar eventos – Quando a outra thread chama o método notifyAll(), a thread acorda e verifica a condição novamente até que existam alguns eventos no armazenamento • Temos que manter a verificação das condições e chamar o método wait() em um laço while, não podemos continuar até que a condição seja verdadeira • Se executarmos esse exemplo, vamos ver como o produtor e o consumidor estão criando e obtendo os eventos, mas o armazenamento nunca possui mais do de 10 eventos
  • 65.
  • 66. Sincronizando um Bloco de Código com um Bloqueio • Java fornece um outro mecanismo para a sincronização de blocos de código • É um mecanismo mais poderoso e flexível do que a palavra-chave synchronized • Ele é baseado na interface Lock e classes que a implementam (como ReentrantLock)
  • 67. Sincronizando um Bloco de Código com um Bloqueio • Este mecanismo apresenta algumas vantagens, que são as seguintes: – Ele permite a estruturação de blocos sincronizados de uma maneira mais flexível. Com a palavra-chave synchronized, temos que obter e liberar o controle sobre um bloco sincronizado de código de uma forma estruturada. As interfaces Lock permitem que obtenhamos estruturas mais complexas para implementar a seção crítica.
  • 68. Sincronizando um Bloco de Código com um Bloqueio • Vantagens (continuação): – As interfaces Lock fornecem funcionalidades adicionais sobre a palavra-chave synchronized. Uma das novas funcionalidades é implementada pelo método tryLock(). Este método tenta obter o controle do bloqueio e se não pode, porque ele está sendo usado por outra thread, ele retorna o bloqueio. Com a palavra-chave synchronized, quando uma thread (A) tenta executar um bloco de código sincronizado, se houver outra thread (B) executando-o, a thread (A) é suspensa até que a thread (B) termine a execução do bloco sincronizado. Com bloqueios, podemos executar o método tryLock(). Esse método retorna um valor booleano que indica se há outra thread executando o código protegido por esse bloqueio.
  • 69. Sincronizando um Bloco de Código com um Bloqueio • Vantagens (continuação): – As interfaces Lock permitem uma separação das operações de leitura e gravação que possuem vários leitores e apenas um modificador. – As interfaces Lock oferecer um melhor desempenho do que a palavra-chave synchronized. • Neste exemplo, vamos aprender como usar bloqueios para sincronizar um bloco de código e criar uma seção crítica usando a interface Lock e a classe ReentrantLock que a implementa, implementando um programa que simula uma fila de impressão
  • 70. Programa Exemplo 1. Crie uma classe chamada PrintQueue que irá implementar a fila de impressão. public class PrintQueue { 2. Declare um objeto Lock e o inicialize com um novo objeto da classe ReentrantLock. private final Lock queueLock=new ReentrantLock(); 3. Implemente o método printJob(). Ele irá receber um Object como parâmetro e não irá retornar nenhum valor. public void printJob(Object document){
  • 71. Programa Exemplo 4. Dentro do método printJob() obtenha o controle do objeto Lock chamando o método lock(). lock() method. queueLock.lock(); 5. Então, inclua o código a seguir para simular a impressão de um documento: try { Long duration=(long)(Math.random()*10000); System.out.println(Thread.currentThread() .getName()+ ":Fila de Impressão: Imprimindo um trabalho durante "+ (duration/1000)+" segundos"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); }
  • 72. Programa Exemplo 6. Finalmente, libere o controle do objeto Lock com o método unlock(). finally { queueLock.unlock(); } 7. Crie uma classe chamada Job e especifique que ela implementa a interface Runnable. public class Job implements Runnable { 8. Declare um objeto da classe PrintQueue e implemente o construtor da classe que inicializa esse objeto. private PrintQueue printQueue; public Job(PrintQueue printQueue){ this.printQueue=printQueue; }
  • 73. Programa Exemplo 9. Implemente o método run(). Ele usa o objeto PrintQueue para enviar um trabalho para a impressora. @Override public void run() { System.out.printf("%s: Imprimindo um documenton", Thread.currentThread() .getName()); printQueue.printJob(new Object()); System.out.printf("%s: O documento foi impresson",Thread.currentThread() .getName()); } 10. Crie a classe principal da aplicação e adicione o método main() a ela. public class Main { public static void main (String args[]){
  • 74. Programa Exemplo 11. Crie um objeto compartilhado PrintQueue. PrintQueue printQueue=new PrintQueue(); 12. Cria 10 objetos Job e 10 threads pra executá-los. Thread thread[]=new Thread[10]; for (int i=0; i<10; i++){ thread[i]=new Thread(new Job(printQueue), "Thread "+ i); } 13. Inicie as 10 threads. for (int i=0; i<10; i++){ thread[i].start(); }
  • 75. Funcionamento • Na captura de tela seguinte, podemos ver uma parte da saída de uma execução, deste exemplo:
  • 76. Funcionamento • A chave para o exemplo é o método printJob() da classe PrintQueue • Quando queremos implementar uma seção crítica usando bloqueios e garantir que apenas uma thread de execução execute um bloco de código, temos que criar um objeto ReentrantLock • No início da seção crítica, temos de começar o controle do bloqueio utilizando o método lock() • Quando uma thread (A) chama este método, se não houver outra thread com o controle do bloqueio, o método permite que a thread (A) controle o bloqueio e retorna imediatamente para permitir a execução da seção crítica por esta thread • Caso contrário, se há uma outra thread (B) executando a seção crítica controlada por este bloqueio, o método lock() coloca a thread (A) para dormir até que a thread (B) termine a execução da seção crítica
  • 77. Funcionamento • No final da seção crítica, temos de usar o método unlock() para liberar o controle do bloqueio e permitir que as outras threads executem esta seção crítica • Se não chamarmos o método unlock() no final da seção crítica, as outras threads que estão aguardando por esse bloco ficarão esperando para sempre, causando uma situação de impasse (deadlock) • Se usarmos blocos try-catch em uma seção crítica, não podemos esquecer de colocar o comando que contém o método unlock() dentro da seção finally
  • 78. Sincronizando um Bloco de Código com um Bloqueio • A interface Lock (e a classe ReentrantLock) inclui um outro método para obter o controle do bloqueio, é o método tryLock() • A maior diferença com o método lock() é que neste método, se a thread que o usa não pode obter o controle da interface Lock, retorna imediatamente e não coloca a thread para dormir • Este método retorna um valor booleano, true se a thread recebe o controle do bloqueio, e false se não • Leve em consideração que é responsabilidade do programador ter em conta o resultado deste método e agir em conformidade
  • 79. Sincronizando um Bloco de Código com um Bloqueio • Se o método retorna o valor false, espera-se que o programa não execute a seção crítica • Se isso acontecer, provavelmente teremos resultados errados na aplicação
  • 80. Sincronizando um Bloco de Código com um Bloqueio • A classe ReentrantLock também permite o uso de chamadas recursivas • Quando uma thread tem o controle de um bloqueio e faz uma chamada recursiva, continua com o controle do bloqueio, de modo que a chamada ao método lock() retornará imediatamente e a thread vai continuar com a execução da chamada recursiva • Além disso, também pode chamar outros métodos
  • 81. Sincronizando um Bloco de Código com um Bloqueio • Devemos ter muito cuidado com o uso de bloqueios para evitar impasses (deadlocks) • Esta situação ocorre quando duas ou mais threads são bloqueadas à espera de bloqueios que nunca vão ser desbloqueados • Por exemplo, uma thread (A) bloqueia um Lock (X) e uma thread (B) bloqueia um Lock (Y) • Se agora, a thread (A) tenta bloquear um Lock (Y) e a thread (B) simultaneamente tenta bloquear um Lock (X), ambas as threads serão bloqueadas indefinidamente, porque elas estão à espera de bloqueios que nunca vão ser liberados • Note-se que o problema ocorre, porque ambas as threads tentam obter os bloqueios na ordem oposta
  • 82.
  • 83. Sincronizando Acesso a Dados com Bloqueios de Leitura/Escrita • Uma das melhorias mais significativas oferecidos por bloqueios é a interface ReadWriteLock e a classe ReentrantReadWriteLock, a única que a implementa • Essa classe tem dois bloqueios, um para operações de leitura e um para operações de gravação • Pode haver mais de uma thread usando operações de leitura simultaneamente, mas somente uma thread pode estar usando operações de gravação • Quando uma thread está fazendo uma operação de gravação, não pode haver qualquer outra thread fazendo operações de leitura • Neste exemplo, vamos aprender como usar uma interface ReadWriteLock implementando um programa que a utiliza para controlar o acesso a um objeto que armazena os preços de dois produtos
  • 84. Programa Exemplo 1. Crie uma classe chamada PricesInfo que armazenará informação sobre os preços de dois produtos. public class PricesInfo { 2. Declare dois atributos double chamados price1 e price2. private double price1; private double price2; 3. Declare um objeto ReadWriteLock chamado lock. private ReadWriteLock lock;
  • 85. Programa Exemplo 4. Implemente o construtor da classe que inicializa estes três atributos. Para o atributo lock, criaremos um novo objeto ReentrantReadWriteLock. public PricesInfo(){ price1=1.0; price2=2.0; lock=new ReentrantReadWriteLock(); }
  • 86. Programa Exemplo 5. Implemente o método getPrice1() que retorna o valor do atributo price1. Ele utiliza o bloqueio de leitura para controlar o acesso ao valor deste atributo. public double getPrice1() { lock.readLock().lock(); double value=price1; lock.readLock().unlock(); return value; }
  • 87. Programa Exemplo 6. Implemente o método getPrice2() que retorna o valor do atributo price2. Ele utiliza o bloqueio de leitura para controlar o acesso ao valor deste atributo. public double getPrice2() { lock.readLock().lock(); double value=price2; lock.readLock().unlock(); return value; }
  • 88. Programa Exemplo 7. Implemente o método setPrices() que estabelece os valores dos dois atributos. Ele utiliza o bloqueio de escrita para controlar o acesso a eles. public void setPrices(double price1, double price2) { lock.writeLock().lock(); this.price1=price1; this.price2=price2; lock.writeLock().unlock(); }
  • 89. Programa Exemplo 8. Crie uma classe chamada Reader e especifique que ela implementa a interface Runnable. Esta classe implementa um leitor de valores dos atributos da classe PricesInfo. public class Reader implements Runnable { 9. Declare um objeto PricesInfo e implemente o construtor da classe que inicializa esse objeto. private PricesInfo pricesInfo; public Reader (PricesInfo pricesInfo){ this.pricesInfo=pricesInfo; }
  • 90. Programa Exemplo 10. Implemente o método run() desta classe. Ele lê 10 vezes o valor dos dois preços. @Override public void run() { for (int i=0; i<10; i++){ System.out.printf("%s: Price 1: %fn", Thread.currentThread().getName(), pricesInfo.getPrice1()); System.out.printf("%s: Price 2: %fn", Thread.currentThread().getName(), pricesInfo.getPrice2()); } }
  • 91. Programa Exemplo 11. Crie uma classe chamada Writer e especifique que ela implementa a interface Runnable. Esta classe implementa um modificador dos valores dos atributos da classe PricesInfo. public class Writer implements Runnable { 12. Declare um objeto PricesInfo e implemente o construtor da classe que inicializa esse objeto. private PricesInfo pricesInfo; public Writer(PricesInfo pricesInfo){ this.pricesInfo=pricesInfo; }
  • 92. Programa Exemplo 13. Implemente o método run(). Ele modifica três vezes o valor dos dois preços e dorme por dois segundos entre as alterações. @Override public void run() { for (int i=0; i<3; i++) { System.out.printf("Writer: Tentando modificar os preços.n"); pricesInfo.setPrices(Math.random()*10, Math.random()*8); System.out.printf("Writer: Os preços foram modificados.n"); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
  • 93. Programa Exemplo 14. Implemente a classe principal do exemplo e crie o método main(). public class Main { public static void main(String[] args) { 15. Crie um objeto PricesInfo. PricesInfo pricesInfo=new PricesInfo(); 16. Crie cinco objetos Reader e cinco Threads para executá- los. Reader readers[]=new Reader[5]; Thread threadsReader[]=new Thread[5]; for (int i=0; i<5; i++){ readers[i]=new Reader(pricesInfo); threadsReader[i]=new Thread(readers[i]); }
  • 94. Programa Exemplo 17. Crie um objeto Writer e uma Thread para executá-lo. Writer writer=new Writer(pricesInfo); Thread threadWriter=new Thread(writer); 18. Inicie as threads. for (int i=0; i<5; i++){ threadsReader[i].start(); } threadWriter.start();
  • 95. Funcionamento • Na captura de tela seguinte, você pode ver uma parte da saída de uma execução deste exemplo:
  • 96. Funcionamento • Como mencionamos anteriormente, a classe ReentrantReadWriteLock tem dois bloqueios, um para operações de leitura e um para operações de gravação • O bloqueio utilizado em operações de leitura é obtida com o método readlock() declarado na interface ReadWriteLock • Esse bloqueio é um objeto que implementa a interface Lock, para que possamos usar os métodos lock(), unlock(), e tryLock() • O bloqueio utilizado em operações de gravação é obtido com o método writeLock() declarado na interface ReadWriteLock
  • 97. Funcionamento • Esse bloqueio é um objeto que implementa a interface Lock, para que possamos usar os métodos lock(), unlock(), e tryLock() • É de responsabilidade do programador garantir o uso correto desses bloqueios, usando-os com os mesmos propósitos para os quais eles foram projetados • Quando conseguimos o bloqueio de leitura de uma interface Lock, não podemos modificar o valor da variável, caso contrário, provavelmente teríamos erros de inconsistência de dados
  • 98.
  • 99. Modificando o Equilíbrio entre Bloqueios • O construtor das classes ReentrantLock e ReentrantReadWriteLock admite um parâmetro boolean chamado fair que permite controlar o comportamento de ambas as classes • O valor false é o valor padrão e é chamado de modo não justo – Neste modo, quando existem algumas threads esperando por um bloqueio (ReentrantLock ou ReentrantReadWriteLock) e o bloqueio tem que selecionar uma delas para obter o acesso à seção crítica, ele seleciona uma sem qualquer critério • O valor true é chamado de modo justo – Neste modo, quando existem algumas threads esperando por um bloqueio (ReentrantLock ou ReentrantReadWriteLock) e o bloqueio tem que selecionar uma delas para obter acesso a uma seção crítica, ele seleciona a thread que está esperando por mais tempo
  • 100. Modificando o Equilíbrio entre Bloqueios • Leve em consideração que o comportamento explicado anteriormente só é usado com os métodos lock() e unlock() • Como o método tryLock() não coloca a thread para dormir, se a interface Lock é usada, o atributo fair não afeta a sua funcionalidade • Neste exemplo, vamos modificar o exemplo implementado em Sincronizando um Bloco de Código com um Bloqueio para usar esse atributo e ver a diferença entre os modos justo e não justo
  • 101. Programa Exemplo 1. Implemente o exemplo explicado em Sincronizando um Bloco de Código com um Bloqueio. 2. Na classe PrintQueue, modifique a construção do objeto Lock. private Lock queueLock=new ReentrantLock(true); 3. Modifique o método printJob(). Separe o simulador de impressão em dois blocos de código, liberando o bloqueio entre eles.
  • 102. Programa Exemplo public void printJob(Object document){ queueLock.lock(); try { Long duration=(long)(Math.random()*10000); System.out.println(Thread.currentThread(). getName()+":Fila de Impressão: Imprimindo um trabalho durante "+ (duration/1000)+" segundos"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } ...
  • 103. Programa Exemplo ... queueLock.lock(); try { Long duration=(long)(Math.random()*10000); System.out.println(Thread.currentThread(). getName()+":Fila de Impressão: Imprimindo um trabalho durante "+ (duration/1000)+" segundos"); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } }
  • 104. Programa Exemplo 4. Modifique a classe principal no bloco que inicia as threads. O novo bloco deve ser: for (int i=0; i<10; i++){ thread[i].start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
  • 105. Funcionamento • Na captura de tela seguinte você pode ver uma parte da saída de uma execução deste exemplo:
  • 106. Funcionamento • Todas as threads são criadas com uma diferença de 0,1 segundo • A primeira thread que solicita o controle do bloqueio é a Thread 0, então a Thread 1, e assim por diante • Enquanto Thread 0 está executando o primeiro bloco de código protegido pelo bloqueio, temos nove threads em espera para executar esse bloco de código • Quando a Thread 0 libera o bloqueio, imediatamente, ela solicita o bloqueio novamente, por isso temos 10 threads que tentam obter o bloqueio • Como o modo justo está habilitado, a interface Lock vai escolher Thread 1, uma vez que é a thread que está esperando por mais tempo pelo bloqueio
  • 107. Funcionamento • Em seguida, escolhe Thread 2, então, o Thread 3, e assim por diante • Até que todas as threads tenham passado pelo primeiro bloco protegido pelo bloqueio, nenhuma delas vai executar o segundo bloco protegido • Uma vez que todas as threads tenham executado o primeiro bloco de código protegido pelo bloqueio, é a vez de Thread 0 novamente, em seguida, é a vez de Thread 1, e assim por diante • Para ver a diferença com o modo não justo, altere o parâmetro passado para o construtor de bloqueio e coloque o valor false
  • 108. Funcionamento • Na captura de tela seguinte, você pode ver o resultado de uma execução do exemplo modificado:
  • 109. Funcionamento • Neste caso, as threads são executadas pela ordem em que foram criadas, mas cada thread executa os dois blocos de código protegidos • No entanto, esse comportamento não é garantido porque, como explicado anteriormente, o bloqueio poderia escolher qualquer thread para dar-lhe acesso ao código protegido • A JVM não garante, neste caso, a ordem de execução das threads • Bloqueios Read/Write também têm o parâmetro fair em seu construtor, o comportamento deste parâmetro neste tipo de bloqueio é o mesmo que foi explicado na introdução deste exemplo
  • 110.
  • 111. Usando Múltiplas Condições em um Bloqueio • Um bloqueio pode ser associado a uma ou mais condições • Estas condições são declaradas na interface Condition • O objetivo destas condições é permitir que threads tenham o controle de um bloqueio e verifiquem se uma condição é verdadeira ou não e, se for falsa, ser suspensa até que uma outra thread a acorde • A interface Condition fornece os mecanismos para suspender uma thread e para acordar uma thread suspensa
  • 112. Usando Múltiplas Condições em um Bloqueio • Um problema clássico na programação concorrente é o problema do produtor-consumidor • Temos um buffer de dados, um ou mais produtores de dados que armazenam no buffer, e um ou mais consumidores de dados que leem a partir do buffer como explicado anteriormente • Neste exemplo, veremos como implementar o problema do produtor-consumidor usando bloqueios e condições
  • 113. Programa Exemplo 1. Primeiro, vamos implementar uma classe que irá simular um arquivo de texto. Crie uma classe chamada FileMock com dois atributos: um array String denominado content e um int chamado index. Eles vão armazenar o conteúdo do arquivo e a linha do arquivo simulado que será recuperada. public class FileMock { private String content[]; private int index;
  • 114. Programa Exemplo 2. Implemente o construtor da classe que inicializa o conteúdo e o arquivo com caracteres aleatórios. public FileMock(int size, int length){ content=new String[size]; for (int i=0; i<size; i++){ StringBuilder buffer = new StringBuilder(length); for (int j=0; j<length; j++){ int indice=(int)Math.random()*255; buffer.append((char)indice); } content[i]=buffer.toString(); } index=0; }
  • 115. Programa Exemplo 3. Implemente o método hasMoreLines() que retorna true se o arquivo tem mais linhas para processar ou false se já atingiu o fim do arquivo simulado. public boolean hasMoreLines(){ return index < content.length; } 4. Implemente o método getLine() que retorna a linha determinada pelo atributo index e aumenta o seu valor. public String getLine(){ if (this.hasMoreLines()) { System.out.println("Mock: "+ (content.length-index)); return content[index++]; } return null; }
  • 116. Programa Exemplo 5. Agora implemente uma classe chamada Buffer que irá implementar o buffer compartilhado por produtores e consumidores. public class Buffer { 6. Esta classe possui seis atributos: – Um atributo LinkedList<String> chamado buffer que irá armazenar os dados compartilhados – Um int chamado maxSize que armazena o tamanho do buffer – Um objeto ReentrantLock chamado lock que controla o acesso aos blocos de código que modificam o buffer – Dois atributos Condition chamados lines e space – Um tipo boolean chamado pendingLines que irá indicar se existem linhas no buffer
  • 117. Programa Exemplo private LinkedList<String> buffer; private int maxSize; private ReentrantLock lock; private Condition lines; private Condition space; private boolean pendingLines;
  • 118. Programa Exemplo 7. Implemente o construtor da classe. Ele inicializa todos os atributos descritos anteriormente. public Buffer(int maxSize) { this.maxSize=maxSize; buffer=new LinkedList<>(); lock=new ReentrantLock(); lines=lock.newCondition(); space=lock.newCondition(); pendingLines=true; }
  • 119. Programa Exemplo 8. Implemente o método insert(). Ele recebe uma String como parâmetro e tenta armazená-lo no buffer. Em primeiro lugar, ele obtém o controle do bloqueio. Quando consegue, então verifica se existe espaço vazio no buffer. Se o buffer está cheio, ele chama o método await() na condição space para esperar por espaço livre. A thread será acordada quando outra thread chamar o método de signal() ou signalAll() na Condition space. Quando isso acontece, a thread armazena a linha no buffer e chama o método signallAll() sobre a condição lines. Como veremos em um momento, essa condição vai acordar todas as threads que estão à espera de linhas no buffer.
  • 120. Programa Exemplo public void insert(String line) { lock.lock(); try { while (buffer.size() == maxSize) { space.await(); } buffer.offer(line); System.out.printf("%s: Linha inserida: %dn", Thread.currentThread().getName(), buffer.size()); lines.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
  • 121. Programa Exemplo 9. Implemente o método get(). Ele retorna a primeira string armazenada no buffer. Em primeiro lugar, ele obtém o controle do bloqueio. Quando consegue, ele verifica se há linhas no buffer. Se o buffer está vazio, ele chama o método await() na condição lines para esperar por linhas no buffer. Esta thread será acordada quando uma outra thread chamar o método signal() ou signalAll() na condição lines. Quando isso acontece, o método obtém a primeira linha no buffer, chama o método signalAll() sobre a condição space e retorna a String.
  • 122. Programa Exemplo public String get() { String line=null; lock.lock(); try { while ((buffer.size() == 0) &&(hasPendingLines())) { lines.await(); } if (hasPendingLines()) { line = buffer.poll(); System.out.printf("%s: Linha carregada: %dn", Thread.currentThread().getName(),buffer.size()); space.signalAll(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return line; }
  • 123. Programa Exemplo 10. Implemente o método setPendingLines() que estabelece o valor do atributo pendingLines. Ele será chamado pelo producer quando não existirem mais linhas para produzir. public void setPendingLines(boolean pendingLines) { this.pendingLines=pendingLines; } 11. Implemente o método hasPendingLines. Ele retorna true se existem mais linhas a serem processadas, ou false caso contrário. public boolean hasPendingLines() { return pendingLines || buffer.size()>0; }
  • 124. Programa Exemplo 12. Implemente uma classe chamada Producer e especifique que ela implementa a interface Runnable. public class Producer implements Runnable { 13. Declare dois atributos: um objeto da classe FileMock e outro objeto da classe Buffer. private FileMock mock; private Buffer buffer; 14. Implemente o construtor da classe que inicializa ambos os atributos. public Producer (FileMock mock, Buffer buffer){ this.mock=mock; this.buffer=buffer; }
  • 125. Programa Exemplo 15. Implemente o método run() que lê todas as linhas criadas no objeto FileMock e usa o método insert() para armazená-las no buffer. Uma vez terminado, use o método setPendingLines() para alertar o buffer que ele não precisa gerar mais linhas. @Override public void run() { buffer.setPendingLines(true); while (mock.hasMoreLines()){ String line=mock.getLine(); buffer.insert(line); } buffer.setPendingLines(false); }
  • 126. Programa Exemplo 16. Implemente uma classe chamada Consumer e especifique que ela implementa a interface Runnable. public class Consumer implements Runnable { 17. Declare um objeto Buffer e implemente o construtor da classe que o inicializa. private Buffer buffer; public Consumer (Buffer buffer) { this.buffer=buffer; }
  • 127. Programa Exemplo 18. Implemente o método run(). Enquanto o buffer possuir linhas pendentes, ele tenta recuperar uma e processá- la. @Override public void run() { while (buffer.hasPendingLines()) { String line=buffer.get(); processLine(line); } }
  • 128. Programa Exemplo 19. Implemente o método auxiliar processLine(). Ele apenas espera por até 10 milissegundos para simular algum tipo de processamento com a linha. private void processLine(String line) { try { Random random=new Random(); Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } }
  • 129. Programa Exemplo 20. Implemente a classe principal do exemplo e adicione o método main(). public class Main { public static void main(String[] args) { 21. Crie um objeto FileMock. FileMock mock=new FileMock(100, 10); 22. Crie um objeto Buffer. Buffer buffer=new Buffer(20); 23. Crie um objeto Producer e uma Thread para executá-lo. Producer producer=new Producer(mock, buffer); Thread threadProducer=new Thread(producer,"Producer");
  • 130. Programa Exemplo 24. Crie três objetos Consumer e três threads para executá- los. Consumer consumers[]=new Consumer[3]; Thread threadConsumers[]=new Thread[3]; for (int i=0; i<3; i++){ consumers[i]=new Consumer(buffer); threadConsumers[i]=new Thread(consumers[i],"Consumer "+i); }
  • 131. Programa Exemplo 25. Inicie o produtor e os três consumidores. threadProducer.start(); for (int i=0; i<3; i++){ threadConsumers[i].start(); }
  • 132. Funcionamento • Todos os objetos Condition estão associados com um bloqueio e são criados usando o método newCondition() declarado na interface Lock • Antes de podermos fazer qualquer operação com uma condição, temos que ter o controle do bloqueio associado com a condição, por isso as operações com condições devem estar em um bloco de código que começa com uma chamada a um método lock() de um objeto Lock e termina com um método unlock() do mesmo objeto Lock • Quando uma thread chama o método await() de uma condição, ela libera automaticamente o controle do bloqueio, de modo que alguma outra thread pode obtê-lo e começar a execução da mesma, ou de outra seção crítica protegida por esse bloqueio
  • 133. Funcionamento • Quando uma thread chama os métodos signal() ou signallAll() de uma condição, uma ou todas as threads que estavam esperando por essa condição serão acordadas, mas isso não garante que a condição que fez com que elas dormissem seja agora verdadeira, por isso devemos colocar a chamada await() dentro de um laço while • Não podemos deixar esse ciclo até que a condição seja verdadeira • Enquanto a condição for falsa, devemos chamar await() novamente
  • 134. Funcionamento • Devemos ter cuidado com o uso de await() e signal(): se chamarmos o método await() em uma condição e nunca chamarmos o método de signal() nesta condição, a thread irá dormir para sempre • Uma thread pode ser interrompida enquanto ele está dormindo, depois de uma chamada ao método await(), então temos que processar a exceção InterruptedException
  • 135. Usando Múltiplas Condições em um Bloqueio • A interface Condition tem outras versões do método await(), que são os seguintes: – await(long time, TimeUnit unit): a thread irá dormir até que: • Seja interrompida • Uma outra thread chama os métodos signal() ou signalAll() na condição • O tempo especificado passe • A classe TimeUnit é uma enumeração com as seguintes constantes: DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS e SECONDS – awaitUninterruptibly(): a thread irá dormir até que uma outra thread chame os métodos signal() ou signalAll(), que não podem ser interrompidos
  • 136. Usando Múltiplas Condições em um Bloqueio • A interface Condition tem outras versões do método await(), que são os seguintes (continuação): – awaitUntil(Date date): a thread ficará dormindo até que: • Seja interrompida • Uma outra thread chama os métodos signal() ou signalAll() na condição • A data especificada chegue • Podemos usar condições com os bloqueios ReadLock e WriteLock de leitura/escrita
  • 137. 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)