Escolar Documentos
Profissional Documentos
Cultura Documentos
Controle de Concorrência
Unidade 3
Se bem utilizado, o paralelismo resulta em
um melhor desempenho dos programas
Controle de Concorrência Mais threads Æ processador melhor utilizado
Monitores No entanto
entanto, podem ocorrer problemas no
acesso concorrente a dados e recursos
Locks
Dados podem se tornar inconsistentes ao
Semáforos serem acessados concorrentemente
Concorrência na API Java (ex.: duas pessoas editando o mesmo arquivo)
Alguns recursos não podem ser compartilhados
(ex.: dois programas usando a impressora)
1
3/4/2008
2
3/4/2008
Monitores Monitores
Histórico Monitores em Java
Proposto por Hoare em 1974;
Classes e objetos podem ser bloqueados;
1a implementação: Pascal Concorrente [1975];
Monitores são definidos usando a palavra-
palavra-
Usado em várias linguagens, inclusive Java;
chave synchronized em blocos ou métodos;
Funcionamento Métodos e blocos synchronized são
“Monitora” o acesso a dados e recursos executados pela JVM com exclusão mútua;
compartilhados por processos e threads;
Threads que tentarem acessar um monitor
Encapsula código que faz o acesso a dados e
recursos monitorados; que esteja em uso entrarão em espera;
Chamadas executadas com exclusão mútua - É criada uma fila de espera pelo monitor;
um acesso por vez - criando uma fila de espera. Threads saem da fila em ordem de prioridade.
Monitores Monitores
Sincronização de Bloco Ex.: Saque com sincronização de bloco
public class Banco {
synchronized (objeto ) { public boolean saque(Conta conta, double valor) {
// código protegido synchronized(conta) {
} double saldo = conta.getSaldo();
if (saldo < valor) {
System.out.println("Saldo insuficiente para o saque.");
O objeto especificado na abertura do bloco é return false;
bloqueado para uso da thread corrente; }
double novoSaldo = saldo - valor;
O código fica protegido de acesso System.out.println(Thread.currentThread().getName() +
" sacou R$"+valor+". Saldo: após saque: R$"+novoSaldo);
concorrente; conta.setSaldo(novoSaldo);
return true;
Qualquer outra thread que tentar bloquear o
}
mesmo objeto entrará em uma fila de espera. }
}
Monitores Monitores
Sincronização de Método Ex.: Conta com sincronização de método
public class Conta {
public synchronized void metodo (int param) { ...
// código protegido public synchronized double getSaldo() { return this.saldo; }
} publicc sy
pub synchronized
c o ed void
o d setSaldo(double
setSa do(doub e s) { tthis.saldo
s sa do = s; }
public synchronized double debitarValor(double valor) {
O objeto usado na invocação é bloqueado if (this.saldo < valor) {
System.out.println("Saldo insuficiente para saque.");
para uso da thread que invocou o método return -1;
Se o método for static, a classe é bloqueada } else { public class Banco {
this.saldo -= valor;
public boolean saque(Conta c, double v) {
Métodos não sincronizados e atributos ainda return this.saldo;
double saldo = c.debitarValor(v);
}
podem ser acessados }
...
}
}
}
3
3/4/2008
Monitores Monitores
Java permite que sejam definidas Exemplo: Produtor, Consumidor e Buffer
condições de acesso dentro de monitores Produtor produz itens e os coloca no buffer
Uma thread em um monitor deve chamar o Consumidor retira e consome os itens
método wait() se não puder prosseguir devido
a alguma
l condição
d ã necessária á não-
não
ã -satisfeita;
f
o acesso ao monitor é então liberado
Sempre que uma condição de espera for
modificada, podemos notificar uma thread em
espera na fila escolhida aleatoriamente com o
método notify()
notify(),, ou notificar todas as threads
na fila chamando o método notifyAll()
Chamá
Chamá--los fora do monitor resulta em exceção
Monitores Monitores
public class ProdCons { // Problema dos Produtores e Consumidores Runnable cons = new Runnable() { // Código do Consumidor
public static java.util.Stack buffer = new java.util.Stack(); // buffer public void run() {
public static final int BUFSIZE = 5; // tamanho máximo do buffer for (int i=0; i<100; i++)
public static void main (String args[]) { synchronized(buffer) { // bloqueia acesso ao buffer
Runnable prod = new Runnable() { // Código do Produtor if (buffer.size() == 0) // se o buffer estiver vazio
public void run() { try { buffer.wait(); } // aguarda item para consumir
for (int i=0; i<100; i++) catch
t h (I
(InterruptedException
t t dE ti e)) {}
synchronized(buffer) { // bloqueia acesso ao buffer int j = ((Integer)buffer.pop()).intValue(); // consome item
if (buffer.size() >= BUFSIZE ) // se o buffer estiver cheio System.out.println("Consome "+j+". Total: "+buffer.size());
try { buffer.wait(); } // aguarda lugar no buffer buffer.notify(); // avisa que lugar foi liberado
catch (InterruptedException e) {} }
buffer.push(new Integer(i)); // põe item no buffer }
System.out.println("Produz "+i+". Total: "+buffer.size()); };
buffer.notify(); // avisa que item foi produzido new Thread (prod).start(); // Inicia o Produtor
} new Thread (cons).start(); // Inicia o Consumidor
} }
}; }
Locks Locks
Interface Lock Outros métodos da interface Lock:
Mecanismo de exclusão mútua tryLock(): bloqueia e retorna true se o lock
tryLock():
Permite somente um acesso por vez estiver disponível, caso contrário retorna false
Caso o dado/recurso esteja em uso,
uso a thread getHoldCount()
getHoldCount():: retorna número de threads
que tentar bloqueá
bloqueá--lo entra numa fila que tentaram obter o lock e não o liberaram
isHeldByCurrentThread()
isHeldByCurrentThread():: retorna true se a
Principais Métodos:
thread que fez a chamada obteve o bloqueio
lock(): primitiva de bloqueio; deve ser
lock():
chamada antes do acesso ao dado/recurso isLocked():: indica se o lock está bloqueado
isLocked()
getQueueLength()
getQueueLength():: retorna o número de
unlock() : primitiva de desbloqueio; usada
para liberar o acesso o dado/recurso threads que aguardam pela liberação do lock
4
3/4/2008
Locks Locks
Classe ReentrantLock Ex.: Classe Conta com ReentrantLock
Implementa mecanismo de bloqueio exclusivo import java.util.concurrent.lock.*;
public class Conta {
Por default a retirada de threads da fila não é private double saldo = 0;
ordenada ou seja,
ordenada, seja não há garantias de quem private
p ate Lock
oc lock
oc = new
e ReentrantLock();
ee t a t oc ();
irá adquirir o lock quando este for liberado public double getSaldo() {
lock.lock();
O construtor ReentrantLock(true) cria um lock try {
return saldo;
com ordenação FIFO da fila, o que torna o } finally {
acesso significativamente mais lento lock.unlock();
}
}
// ... idem para os demais métodos
}
Locks Locks
Interface ReadWriteLock Ex.: Conta com ReentrantReadWriteLock
Possui dois Locks:
Locks: import java.util.concurrent.locks.*;
public class Conta {
readLock
readLock()
():: para acesso compartilhado de private double saldo = 0;
threads com direito de leitura (acesso) private ReadWriteLock lock = new ReentrantReadWriteLock();
public double getSaldo() {
writeLock
writeLock()
():: para acesso exclusivo de uma lock.readLock().lock();
thread com direito de escrita (modificação) try { return this.saldo; }
finally { lock.readLock.unlock(); }
Implementada por ReentrantReadWriteLock }
public double setSaldo(double saldo) {
Por default não garante a ordem de liberação lock.writeLock().lock();
nem preferência entre leitores e escritores try { this.saldo = saldo; }
finally { lock.writeLock.unlock(); }
Ordenação FIFO é garantida passando o valor }
‘true’
true’ para o construtor da classe // ... idem para debitarValor()
}
Locks Locks
public class Filosofo extends Thread{
Exemplo: Jantar dos Filósofos private static Lock garfo[] = new Lock[5];
private int id = 1;
public Filosofo(int id) { super("Filosofo-"+id); this.id = id; }
public void run() {
while(true)
( ) tryy {
sleep((long)(Math.random()*5000)); // Pensando...
garfo[id-1].lock(); // Pega garfo à sua esquerda
garfo[id%5].lock(); // Pega garfo à sua direita
sleep((long)(Math.random()*5000)); // Comendo...
} catch(InterruptedException ie) {}
finally {
garfo[id-1].unlock(); // Solta garfo à sua esquerda
garfo[id%5].unlock(); //Solta garfo à sua direita
}
}
}
5
3/4/2008
Locks Locks
Deadlock Jantar dos filósofos
Caso os cinco filósofos peguem o garfo da Cada processo deve ter um identificador (1 a
esquerda, nenhum deles conseguirá comer 5)
Esta situação é chamada de deadlock Os pares tentam pegar primeiro o garfo a
Deadlock ocorre quando, em um grupo de esquerda
processos/threads em espera, uma aguarda o Os impares tentam pegar primeiro o garfo a
término da outra para que possa prosseguir direita
Em Java, as threads ficarão em espera
indefinidamente
Algumas linguagens/sistemas detectam o
deadlock e reportam exceções
Locks Locks
Detecção de Deadlock Recuperação de Deadlock
Verificar o estado do sistema periodicamente Ao detectar um deadlock, deve-
deve-se abortar uma
para determinar se ocorreu deadlock isLocked
isLocked()
() thread/processo para quebrar o ciclo de espera
Precisa saber que bloqueios estão ativos Thread/processo abortado pode ser reiniciado
Deadlocks são detectados usando gráfico de Critérios possíveis de escolha da vítima:
espera, no qual um ciclo indica um deadlock Tempo em que iniciou o processamento
Tempo necessário para sua conclusão
T1 T2 T4 T5
Operações de I/O já efetuadas ou a efetuar
Número de abortos sofridos (para evitar que
T3 T6
a vítima seja sempre a mesma)
Gráfico sem ciclo Gráfico com ciclo etc.
Locks Locks
public class CircularBuffer implements Buffer { // © Deitel & Assoc.
Locks podem ser associados a condições private int[] buffer = { -1, -1, -1 }; // buffer com 3 lugares
de acesso usando a classe Condition private int occupiedBuffers = 0, writeIndex = 0, readIndex = 0;
private Lock lock = new ReentrantLock(); // lock de acesso ao buffer
Seu uso é mais flexível que em monitores private Condition canWrite = lock.newCondition(); // condição p/ ler
private Condition canRead = lock.newCondition(); // cond. p/ escrita
Condição
ç deve ser associada a um Lock public void set(int value) { // coloca um valor no buffer
Criada com o método newCondition() de Lock lock.lock(); // bloqueia o acesso ao buffer
try {
Principais métodos: while ( occupiedBuffers == buffer.length ) // buffer cheio
canWrite.await(); // espera que haja lugar no buffer
await()
await():: aguarda condição ser alterada; buffer[ writeIndex ] = value; // coloca valor no buffer
tempo limite de espera pode ser estipulado writeIndex=(writeIndex+1)%buffer.length; // calc. próx. posição
occupiedBuffers++; // mais um lugar foi ocupado
signal()
signal():: sinaliza que houve alteração da canRead.signal(); // avisa threads esperando para ler do buffer
condição, tirando uma thread da fila } catch (InterruptedException e) { e.printStackTrace(); }
finally { lock.unlock(); } // libera acesso ao buffer
signalAll()
signalAll():: retira todas as threads da fila } // fim do método set
6
3/4/2008
Locks Semáforos
// Continuação da classe CircularBuffer
public int get() { // retira valor do buffer Permite controlar o número de acessos
int readValue = 0; // valor que será lido do buffer
lock.lock(); // bloqueia acesso ao buffer
simultâneos a um dado ou recurso
try {
while ( occupiedBuffers == 0 ) // se o buffer estiver vazio
Métodos da classe Semaphore
canRead.await();
ca ead a a t(); // agua
aguarda
da que haja
aja uum valor
a o pa
para
a se
ser lido
do Semaphore(int acessos [, boolean ordem]
ordem]):
readValue = buffer[ readIndex ]; // lê um valor do buffer
readIndex =(readIndex+1)%buffer.length; // calc. próx. posição
construtor; parâmetros definem o número de
occupiedBuffers--; // um lugar foi liberado acessos simultâneos possíveis e se a ordem de
canWrite.signal(); // avisa threads esperando para escrever liberação de threads em espera será FIFO
} catch ( InterruptedException e ) { e.printStackTrace(); }
finally { acquire()
acquire():: solicita acesso a um dado ou
lock.unlock(); // libera acesso ao buffer recurso, entrando em espera se todos os
}
return readValue; direitos de acesso estiverem sendo usados
} // fim do método get
release()
release():: libera um direito de acesso
} // fim da classe CircularBuffer
Semáforos Semáforos
import java.util.concurrent.*;
Exemplo: Estacionamento public class Carro extends Thread {
private static Semaphore estacionamento = new Semaphore(10,true);
public Carro(String nome) { super(nome); }
public void run() {
try {
estacionamento acquire();
estacionamento.acquire();
System.out.println(getName() + " ocupou vaga.");
sleep((long)(Math.random() * 10000));
System.out.println(getName() + " liberou vaga.");
estacionamento.release();
} catch(InterruptedException ie){ ie.printStackTrace(); }
}
public static void main(String args[]) {
for (int i = 0; i< 20; i++)
new Carro("Carro #"+i).start();
}
}
7
3/4/2008
8
3/4/2008