Você está na página 1de 9

3/4/2008

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)

Controle de Concorrência Controle de Concorrência


„ Exemplo::
Exemplo „ Exemplo: Conta Bancária
„ Suponha que X é um dado compartilhado,
compartilhado,
com valor inicial 0 Pai Mae Filho
P1 P2 P3
... ... ...
X=X+1 X=X+2 X=X+3
... ... ...
Banco
„ Qual o valor final de X?
„ Assembly:
Load X Load X Load X Conta
Add 1 Add 2 Add 3
Store X Store X Store X

Controle de Concorrência Controle de Concorrência


„ Exemplo: Família com conta conjunta „ Exemplo: Clientes do Banco
public class Familia { public class Cliente extends Thread {
public static void main (String args[]) { private static Banco banco = new Banco();
// Cria conta conjunta da família private Conta conta = null;
final
a Conta
Co ta coconta
ta = new
e CoConta(100000);
ta( 00000); private
p ate doub
doublee valor
a o = 100;
00;
// Cria familiares e lhes informa a conta conjunta public Cliente(String nome, Conta conta) {
Cliente pai = new Cliente("Pai ", conta); super(nome);
Cliente mae = new Cliente("Mãe ", conta); this.conta = conta;
Cliente filho = new Cliente("Filho ", conta); }
// Inicia as threads public void run() {
pai.start(); double total = 0;
mae.start(); while (banco.saque(conta,valor))
filho.start(); total += valor;
} System.out.println(getName() + " sacou total de R$" + total);
} }
}

1
3/4/2008

Controle de Concorrência Controle de Concorrência


„ Exemplo: Classe Banco „ Exemplo: Conta Bancária
public class Banco { public class Conta {
public boolean saque(Conta conta, double valor) { public double saldo = 0;
double saldo = conta.getSaldo();
public Conta(double saldo) {
if (sa
(saldo
do < valor)
ao ) {
this saldo = saldo;
this.saldo
System.out.println("Saldo insuficiente para o saque.");
System.out.println("Conta criada. Saldo inicial: R$" + saldo);
return false;
}
}
double novoSaldo = saldo - valor; public double getSaldo() {
System.out.println(Thread.currentThread().getName() + return saldo;
" sacou R$"+valor+". Saldo após saque: R$"+novoSaldo); }
conta.setSaldo(novoSaldo);
public void setSaldo(double saldo) {
return true;
this.saldo = saldo;
}
}
}
}

Controle de Concorrência Controle de Concorrência


„ Ex.: Movimentação da Conta Conjunta „ O acesso simultâneo / concorrente à conta
Conta criada. Saldo inicial: R$100000.0 pode causar inconsistências nos dados
Pai sacou R$100.0. Saldo após o saque: R$99900.0
Pai sacou R$100.0. Saldo após o saque: R$99800.0 „ Caso ocorra uma troca de contexto durante a
Pai sacou R$100.0. Saldo após o saque: R$99700.0 execução do método Banco.saque() entre a
Pai sacou R$100.0.
R$100 0 Saldo após o saque: R$99600.0
R$99600 0 l
leitura do
d saldo
ld e a sua atualização,
l ã e, antes
Pai sacou R$100.0. Saldo após o saque: R$99500.0
Pai sacou R$100.0. Saldo após o saque: R$99400.0 da finalização do saque, uma outra thread
Pai sacou R$100.0. Saldo após o saque: R$99300.0 altere o valor do saldo, esta alteração será
Pai sacou R$100.0. Saldo após o saque: R$99200.0
Pai sacou R$100.0. Saldo após o saque: R$99100.0 ignorada, pois o saldo após o saque será
Mãe sacou R$100.0. Saldo após o saque: R$99100.0 calculado com base no valor lido inicialmente
Filho sacou R$100.0. Saldo após o saque: R$99100.0
Pai sacou R$100.0. Saldo após o saque: R$99000.0 „ É preciso evitar que outras threads alterem o
Pai sacou R$100.0. Saldo após o saque: R$98900.0 saldo desta conta enquanto um saque estiver
Mãe sacou R$100.0. Saldo após o saque: R$98900.0
... em andamento

Controle de Concorrência Controle de Concorrência


„ Problemas causados pela concorrência „ Mecanismos de Controle de Concorrência
„ Devido a inconsistências que podem ocorrer disponíveis em Java:
com código paralelo,
paralelo, é preciso fazer o controle „ Monitor:: protege trechos de código/
Monitor código/métodos
de concorrência entre processos e threads que manipulam dados/recursos
dados/recursos comparti
comparti--
„ Mecanismos de Controle de Concorrência lh d , impedindo
lhados,
lhados d d o acesso concorrente
concorrente;;
„ Limitam o acesso concorrente a dados e „ Lock (ou Mutex)
Mutex): cria uma fila de acesso a um
recursos compartilhados dado/recurso
dado/ recurso compartilhado
compartilhado,, impedindo o
acesso concorrente
concorrente;;
„ Garantem o isolamento entre processos e
threads concorrentes „ Semáforo
Semáforo:: limita o número de usuários que
acessam simultaneamente um recurso, criando
„ Buscam evitar inconsistências nos dados
filas de acesso se este número for excedido.
causadas pelo acesso concorrente

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();
}
}

Concorrência na API Java Concorrência na API Java


„ Algumas classes da API Java controlam a „ Para evitar acesso concorrente a classes
concorrência internamente Æ thread safe thread unsafe, podemos criar novas
„ Ex.: Vector, Hashtable, ... classes protegidas que as encapsulem
„ Outras classes são não fazem o controle public class SynchronizedLinkedList {
LinkedList lista = new LinkedList();
„ São thread unsafe, ou seja, não garantem a public synchronized void add(Object o) {
sua consistência se usadas por várias threads lista.add(o);
}
„ Estas classes são em geral mais rápidas, pois
public synchronized Object get(int index) {
controle de concorrência reduz o desempenho return lista.get(index);
„ Classes thread unsafe devem ser protegidas }
se forem usadas por mais de uma thread // idem para os demais métodos de LinkedList
}
„ Ex.: componentes do Swing, LinkedList, ...

7
3/4/2008

Concorrência na API Java Concorrência na API Java


„ Mesmo ao usar classes da API thread safe, „ Interface BlockingQueue<E>
é preciso tomar cuidado ao utilizá-
utilizá-las „ Fornece métodos para acesso a uma fila de
elementos genérica que bloqueia automatica-
automatica-
Vector v = new Vector(); Vector v = new Vector(); mente se alguma condição impedir o acesso
Object o; Object o; „ Principais métodos:
... ...
// Percorre o Vetor synchronized(v) { // Bloqueia v „ put() coloca elemento na fila, aguardando
for (int i=0; i<v.size();i++) { // Percorre o Vetor se ela estiver cheia
o = v.get(i); /* Pode causar for (int i=0; i<v.size();i++) { „ take() retira o elemento da fila, aguardando
ArrayIndexOutOfBounds */ o = v.get(i);
... ... se ela estiver vazia
} } „ remainingCapacity() informa o número de
} // Libera o acesso ao Vetor lugares restantes na fila

Concorrência na API Java Concorrência na API Java


import java.util.concurrent.ArrayBlockingQueue;
„ Implementações de BlockingQueue public class BlockingBuffer implements Buffer { // © Deitel & Assoc.
private BlockingQueue<Integer> buffer =
„ ArrayBlockingQueue: array bloqueante
ArrayBlockingQueue: new ArrayBlockingQueue<Integer>(3); // buffer de 3 lugares
„ DelayQueue:: fila na qual um elemento só
DelayQueue public void set(int value) { // coloca um valor no buffer
try {
pode ser retirado após seu delay expirar buffer put(value);
buffer.put(value); // coloca valor no buffer
} catch (InterruptedException e) { e.printStackTrace(); }
„ LinkedBlockingQueue
LinkedBlockingQueue:: fila encadeada } // fim do método set
bloqueante public int get() { // retira valor do buffer
int readValue = 0; // valor que será lido do buffer
„ PriorityBlockingQueue
PriorityBlockingQueue:: fila bloqueante com try {
acesso aos elementos em ordem de prioridade readValue = buffer.take(); // lê um valor do buffer
} catch ( InterruptedException e ) { e.printStackTrace(); }
„ SynchronousQueue
SynchronousQueue:: cada put() é sincronizado return readValue;
} // fim do método get
com um take() } // fim da classe BlockingBuffer

Concorrência na API Java Concorrência na API Java


„ O pacote java.util.concurrent.atomic „ Exemplos de métodos dos tipos atômicos
fornece classes thread safe equivalentes a „ get(), set():
get(), set(): retorna/altera valor atomicamente
alguns tipos de dados do Java: „ compareAndSet():: compara o valor e, caso
compareAndSet()
„ AtomicBoolean seja igual
igual, o modifica
„ AtomicInteger e AtomicIntegerArray „ getAndAdd()
getAndAdd():: retorna valor atual e adiciona
„ AtomicLong e AtomicLongArray „ addAndGet()
addAndGet():: adiciona e retorna novo valor
„ AtomicReference e AtomicReferenceArray „ getAndDecrement()
getAndDecrement(),, getAndIncrement()
getAndIncrement()::
„ etc. retorna valor atual e decrementa/incrementa
„ decrementAndGet()
decrementAndGet(),, incrementAndGet()
incrementAndGet()::
decrementa/incrementa e retorna novo valor

8
3/4/2008

Concorrência na API Java Concorrência na API Java


„ Componentes Swing e Threads „ Ex.: Acesso a componentes Swing em threads
„ Componentes Swing não são thread safe ...
private static java.util.Random generator = new Random();
Torná--los thread safe reduziria o desempenho
„ Torná private javax.swing.JLabel output = new javax.swing.JLabel();
final String threadName = Thread.currentThread().getName();
„ Todas as alterações
ç em componentes
p devem ...
// GGera um caractere
t aleatoriamente
l t i t ed depois
i mostra
t na ttela
l
ser efetuadas pela thread de despacho de javax.swing.SwingUtilities.invokeLater(
eventos, ou podem ocorrer inconsistências new Runnable() {
public void run() {
„ Alterações são agendadas para serem // gera caractere aleatório
executadas pela thread de despacho usando o char displayChar = (char) (generator.nextInt(26) + 'A');
// mostra o caractere no JLabel output
método SwingUtilities.invokeLater(Runnable r) output.setText( threadName + ": " + displayChar );
„ Alterações em componentes devem estar no } // fim do método run
} // fim da classe interna anônima
código do método run() do Runnable ); // fim da chamada a SwingUtilities.invokeLater
...