Buscar

aula 03 Sincronizacao Threads

Prévia do material em texto

Programação Concorrente
Threads em Java
Prof. Antonio Felipe Podgorski Bezerra M.sC
afbezerra@unicarioca.edu.br
Sumário
Estratégias para solução de problemas com Threads.
- Exclusão Mútua
- Locks
- Objetos imutáveis
Sincronização entre threads em Java
— Problema: inconsistência de dados.
— Em outras palavras...
— Condição de corrida: operações sobre recursos 
compartilhados que podem levar a inconsistências 
dependendo da ordem de execução.
— Seção crítica: trecho do programa que contém operações 
que podem levar a inconsistências
— Solução: exclusão mútua
— Em outras palavras...
— Garantir atomicidade e acesso exclusivo ao recurso na 
seção crítica.
Condições de Corrida
— Threads acessam uma variável compartilhada ao 
mesmo tempo.
— As threads escrevem e lêem a variável.
— O resultado de uma thread pode sobrescrever o da 
outra.
— O resultado da computação pode variar.
Condições de corrida:
Exemplo
Thread T1: Pega o valor .
Thread T2: Pega o valor .
Thread T1: Incrementa o valor; resultado: 1.
Thread T2: Decrementa o valor; resultado: -
1
Thread T1: Armazena o resultado: 1.
Thread T2: Armazena o resultado: -1.
Exemplo:
código não sincronizado 
Entre a leitura e o 
armazenamento, a 
thread atual pode 
ser interrompida!
Operação não-atômica!
1.Valor de contador é lido
(possivelmente guardado 
em um registrador)
2.Valor lido é incrementado 
e armazenado (na 
memória)
Seção Crítica (Região Crítica)
— Em programação concorrente, uma região crítica é
uma área de código de um algoritmos que acessa
um recurso compartilhado que não pode ser
acessado concorrentemente por mais de uma linha
de execução.
— Ocorre quando:
— Sistema composto por N processos (onde N > 1).
— Cada processo executa seu próprio código 
independente dos demais.
— Processos compartilham um dado recurso.
— O estado de um processo é desconhecido pelo 
outro.
— O estado/valor do recurso pode ser alterado.
Seção Crítica: Exemplo
public void T0 ( ) {
long i;
for (i=0; i<1000000; i++) {
a = a + 5;
}
System.out.println(“Encerrei a
T0”);
}
public void T1 ( ) {
long i;
for (i=0; i<1000000; i++) {
a = a + 2;
}
System.out.println(“Encerrei a
T1”);
}
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
— Uma estratégia simples de prevenção de 
interferência de threads e erros de consistência de 
memória.
— Campos final, que não podem ser modificados após 
a construção do objeto, podem ser lidos de maneira 
segura.
Sincronização entre threads
em Java
Sincronização entre threads
em Java
— Java oferece duas formas básicas de sincronização:
— Métodos sincronizados
— Blocos sincronizados
Métodos sincronizados
— Através da palavra synchronized
Reduzir o custo 
de sincronização 
associado ao 
método.
Blocos sincronizados
Blocos sincronizados
Poder combinar mais de 
um objeto no momento da 
sincronização.
Sincronização entre threads
em Java
— Competição no acesso a dados compartilhados.
Variável de 
classe
(STATIC)
e/ou
Referência 
para um 
mesmo 
objeto
Sincronização entre threads
em Java
— Situação típica: duas ou mais threads fazem 
operação sobre dado compartilhado.
Sincronização entre 
threads
em Java
— Situação 1: T1 primeiro e T2 depois.
— Situação 1: Não há problemas de inconsistência...
Sincronização entre threads
em Java
— Situação 2: T1 e T2 concorrentemente.
— Situação 2: Há problemas de inconsistência...
Exclusão Mútua em Java
— Compartilhamento do objeto.
public static void main(String[] args) {
ContaBancaria c = new ContaBancaria(1000f);
ThreadDeposita td = new ThreadDeposita(c);
ThreadRetira tr = new ThreadRetira(c);
td.start();
tr.start();
}
Exclusão Mútua em Java
— Thread faz acesso ao objeto.
public class ThreadDeposita extends Thread {
private ContaBancaria c;
public ThreadDeposita(ContaBancaria c) {
this.c = c;
}
public void run() {
for (int i = 0; i < 5; i++)
c.deposita(300f);
}
}
Exclusão Mútua em Java
— Thread faz acesso ao objeto.
public class ThreadRetira extends Thread {
private ContaBancaria c;
public ThreadRetira(ContaBancaria c) {
this.c = c;
}
public void run() {
for (int i = 0; i < 5; i++)
c.retira(100f);
}
}
Exclusão Mútua em Java
— Sem synchronized.
class ContaBancaria {
private float saldo;
public ContaBancaria(float v) { saldo = v; }
public float getSaldo() { return saldo; }
void deposita(float v) {
saldo += v; 
System.out.println("Depósito: R$ 300,00 - Saldo: " + getSaldo());}
void retira(float v) {
saldo -= v; 
System.out.println("Retirada: R$ 100,00 - Saldo: " + getSaldo());}
}
Exclusão Mútua em Java
— Uso de synchronized.
class ContaBancaria {
private float saldo;
public ContaBancaria(float v) { saldo = v; }
public float getSaldo() { return saldo; }
synchronized void deposita(float v) {
saldo += v; 
System.out.println("Depósito: R$ 300,00 - Saldo: " + getSaldo());}
synchronized void retira(float v) {
saldo -= v; 
System.out.println("Retirada: R$ 100,00 - Saldo: " + getSaldo());}
}
Métodos
synchronized
executam
em exclusão mútua
sobre o mesmo 
objeto
compartilhado
Exclusão Mútua em Java
— Métodos synchronized em um mesmo objeto são 
executados em exclusão mútua.
— Só têm efeito em objetos compartilhados (mais de 1 
thread referenciando mesmo objeto).
— Limitação à concorrência.
— Usar com cuidado.
Monitores
— São objetos que garantem a exclusão mútua na 
execução dos procedimentos associados a eles.
— Apenas um procedimento associado ao monitor 
pode ser executado em um determinado momento.
— Em Java todo objeto possui um monitor associado. 
Sincronização usando Monitores
— Declarando uma seção de código sincronizada
Sincronização usando 
Monitores
— Declarando o método todo sincronizado.
Sincronização usando 
Monitores
Código com 
problemas no uso de 
monitores
Sincronização usando Monitores
— Código corrigido:
Sincronização usando Monitores
— Faz uso dos métodos Wait() e notify() para gerar 
eventos de espera e notificação para parar e esperar.
Erros de consistência na memória
— Quando as threads possuem diferentes 
visões de um determinado dado.
— Exemplo:
— Uma variável inteiro compartilhada 
entre as threads
— int contador = 0;
— Thread A incrementa a variável
— contador++;
— Thread B imprime a variável
— System.out.println(contador);
Modificador volatile
— Variável pode ser acessada/modificada por mais de 
uma thread.
— O valor da variável nunca será guardado localmente 
pela thread.
— Garante que escrita e leitura são realizadas 
diretamente na memória principal.
Quando usar?
— Para escrever em uma variável, como uma flag, em 
uma thread.
— Para checar essa variável em outra thread.
— O valor de escrita não depende do valor corrente.
— Não há preocupações em perder o valor atualizado.
— Para campos que são imutáveis (final).
— Para variáveis que são acessadas por uma única 
thread.
— Para operações complexas, como quando é 
necessário prevenir acesso a uma variável por um 
certo tempo.
Quando NÃO usar?
Exemplo
https://www.youtube.co
m/watch?v=Ult9o4b0y9s
Threads stop using volatile
variable flags
Deadlock
— Descreve uma situação em que dois ou mais Threads
são bloqueados para sempre esperando um ao
outro.
— Ocorre quando várias threads necessitam dos
mesmos bloqueios, mas os obtém em ordem
diferente.
— Nas Multithreads em java podem ocorrer deadlocks
porque a palavra-chave syncronized causa o
bloqueio da thread em execução,enquanto
aguarda o bloqueio associado ao objeto
especificado.
Deadlock - Exemplo
public class TestDeadlockExample1 {
public static void main(String[] args) {
final String resource1 = "ratan jaiswal";
final String resource2 = "vimal jaiswal";
// t1 tries to lock resource1 then resource2
Thread t1 = new Thread() {
public void run() {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1");
try {
Thread.sleep(10);
} catch (InterrupedException e) { }
System.out.println("Thread 1: Waiting for resource 2"); 
synchronized (resource2) {
System.out.println("Thread 1: Holding resources 1 & 2");
}
}
}
};
Deadlock - Exemplo
// t2 tries to lock resource2 then resource1
Thread t2 = new Thread() {
public void run() {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2");
try {
Thread.sleep(10);
} catch (InterruptedException e) { }
System.out.println("Thread 2: Waiting for resource 1");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 & 2");
}
}
}
};
t1.start();
t2.start();
}
}
Thread 1: Holding resource 1...
Thread 1: Waiting for resource 2...
Thread 1: Holding resource 1 & 2...
Thread 2: Holding resource 1...
Thread 2: Waiting for resource 2...
Thread 2: Holding resource 1 & 2... 
Deadlock - Solução
// t2 tries to lock resource2 then resource1
Thread t2 = new Thread() {
public void run() {
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1");
try {
Thread.sleep(10);
} catch (InterruptedException e) { }
System.out.println("Thread 2: Waiting for resource 2");
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 1 & 2");
} 
}
}
};
t1.start();
t2.start();
}
}
Então, apenas modifique a ordem dos RESOURCES e das mensagens. Isso impede 
que o programa entre em deadlock. 
Thread 1: Holding resource 1...
Thread 1: Waiting for resource 
2...
Thread 1: Holding resource 1 & 
2...
Thread 2: Holding resource 1...
Thread 2: Waiting for resource 
2...
Thread 2: Holding resource 1 & 
2...
Locks
— A interface Lock pode ser usada no lugar 
de blocos sincronizados .
— Oferecem maior flexibilidade.
— Para criar um lock explícito, você instancia 
uma implementação da interface Lock.
— Normalmente, instancia-se ReentrantLock
— Para obter o lock, usa-se o método lock()
— Para liberar o lock, usa-se unlock()
public class Example {
private Lock aLock = new ReentrantLock();
public int get(int who) {
aLock.lock();
try{
//Acessar recursos compartilhados...
} finally {
aLock.unlock();
}
}
}
Locks: Sintaxe Básica
— Como garantir que o lock seja sempre liberado?
— Já que o lock não é liberado automaticamente, é 
essencial que o corpo do método esteja coberto por 
try/finally para garantir que o lock seja liberado
Locks
Variáveis condicionais
— Para esperar por um lock explícito, cria-se uma 
variável condicional.
— Um objeto que implementa a interface Condition.
— Usar Lock.newCondition() para criar uma condição
— A condição provê os métodos:
— await() para esperar até a condição ser 
verdadeira
— signal() e signalAll() para avisar aos threads que 
a condição ocorreu.
Exemplo
Blocos Guardados
(Guarded Blocks)
— Thread frequentemente têm que coordenar suas ações.
— Guarded blocks é um mecanismo comum para 
coordenação.
— Um bloco só é executado caso uma determinada 
condição seja verdadeira.
Blocos Guardado com eficiência
Notificando as threads
— Notificar é retirar do estado de espera.
— notify(): Notifica uma thread que esteja esperando 
em um lock.
— Não se especifica qual thread vai ser notificada.
— nofityAll(): Notifica todas as threads que estejam 
esperando em um lock.
Quando usar notify()?
— Acordar uma única thread.
— Não é possível especificar qual thread é acordada.
— Útil para sistemas massivamente paralelo:
— Grande número de threads.
— Todas fazendo tarefas similares.
— Então, não importa qual é acordada.
Alguns detalhes
— Notificações: 
— Devem ser usados na implementação 
das operações dos objetos sincronizados.
— Evitando Starvation:
— Certifique que as threads não fiquem em 
espera eternamente.
— Notifique-as! 
Exemplo de fluxo de notificação
— Thread A invoca o método wait().
— O lock é liberado.
— Execução suspensa.
— Outra thread adquire o mesmo lock.
— Ela invoca o notifyAll() .
— Notifica todas as threads que esperam pelo lock.
Exemplo de Fluxo
Objetos imutáveis
— O estado do objeto não pode ser alterado após sua 
construção.
— São muito úteis em aplicações concorrentes.
— Não são corrompidos por interferência de threads.
— São consistentes.
Estratégia de definição de um
objeto imutável
— Não fornecer os métodos “set”.
— Fazer todos os atributos como final e private.
— Não permitir subclasses sobrescrever métodos.
— Declarando a classe como final.
— Fazer o construtor privado e construir instancias em 
fábricas de métodos.
— Caso algum atributo seja uma instância de um objeto 
mutável, não permitir que esses objetos sejam 
mudados:
— Não fornecer métodos que modifica os objetos 
mutáveis.
— Se necessário, crie cópias e armazene as 
referências para as cópias.
Estratégia de definição de um objeto
imutável
Classe Imutável: Exemplo
Exercícios práticos
— Crie um classe ContadorTempo que possui um 
atributo tick inteiro e um método nextTick()
— Crie um classe Relogio que possui um 
contadorTempo e atraves de uma thread chama o 
metodo nextTick a cada segundo
— Crie um classe Cronometro que herda de 
ContadorTempo e através de uma thread chama o 
nextTick()
Exercícios práticos
— Implementar os exemplos do link abaixo, inclusive os 
3 exercícios propostos ao final.
http://docs.oracle.com/javase/tutorial/essential/concu
rrency/
Referências
´Java - Como programar, de Harvey M. 
Deitel, 8ª edição.
´Use a cabeça! - Java, de Bert Bates e 
Kathy Sierra.
´Effective Java Programming 
Language Guide, de Joshua Bloch.
´http://docs.oracle.com/javase/tutorial
/essential/concurrency/

Continue navegando