Java 2 - Concorrência em Threads
Prof. Ricardo Linden
Cocorrência em Threads
1
Condição de Corrida (race conditions)
• Em um mundo concorrente, sempre
assuma que alguém está acessando
seus objetos sem você saber.
• Todas as threads do mundo são suas
adversárias.
• Exemplo. Considere o que pode
acontecer quando lendo e escrevendo
simultaneamente.
Thread 1
Thread 2
f1 = a.field1
a.field1 = 1
f2 = a.field2
a.field2 = 2
Cocorrência em Threads
2
Races
• Suponha que e a Thread 1 começa primeiro
• Thread 1 lê valores originais
Thread 1
Thread 2
f1 = a.field1
a.field1 = 1
f2 = a.field2
a.field2 = 2
Cocorrência em Threads
3
Races
• Suponha que a Thread 2 vai primeiro
• A Thread 1 lê os novos valores
Thread 1
Thread 2
f1 = a.field1
a.field1 = 1
f2 = a.field2
a.field2 = 2
Cocorrência em Threads
4
Races
• Thread 2 vai primeiro, mas é interrompida no meio
• A Thread 1 vê um valor novo, um antigo.
Thread 1
Thread 2
f1 = a.field1
a.field1 = 1
f2 = a.field2
a.field2 = 2
Cocorrência em Threads
5
Operações Atômicas
• São operações que não podem ser quebradas em operações
menores.
• Para todos os efeitos, são tratadas como se fossem uma única
operação.
• Logo, uma thread não pode ser interrompida no meio de sua
execução, já que não existe como quebrá-las.
Cocorrência em Threads
6
Operações não atômicas
• Leituras e escritas de 32-bit são garatidamente atômicas
• Operações de 64-bit podem não sê-lo.
• Logo,
int i; double d;
Thread 1
Thread 2
i = 10;
i = 20;
d = 10.0;
d = 20.0;
i conterá 10 ou 20
d pode conter 10, 20 ou lixo.
Cocorrência em Threads
7
Acesso Sincronizado a Dados Compartilhados
• Considere uma classe Banco que proveja um método de
transferência que mude o saldo de duas contas.
• Se múltiplas threads Transaction querem ter acesso ao mesmo
objeto de classe Banco problemas podem surgir.
Cocorrência em Threads
8
Código Fonte
class Bank {
public static final int NTEST = 10000;
private final int[] accounts;
private long ntransacts = 0;
public Bank(int n, int initialBalance)
{
accounts = new int[n];
for (int i=0;i<accounts.length;i++)
accounts[i] = initialBalance;
ntransacts = 0;
}
public void transfer(int from, int to, int amount)
throws InterruptedException
{
if (accounts[from] < amount) return;
accounts[from] -= amount;
accounts[to] += amount;
ntransacts++;
if (ntransacts % NTEST == 0) test();
}
Cocorrência em Threads
}
9
Operações Atômicas
• Lembrando a definição : São operações que para todos os efeitos,
são tratadas como se fossem uma única operação.
• Logo, uma thread não pode ser interrompida no meio de sua
execução, já que não existe como quebrá-las.
• No método transfer, as operações marcadas em vermelho, apesar
de serem uma única linha de código, não são operações atômicas
quando compiladas para byte-code.
Cocorrência em Threads
10
Operações não atômicas em Threads
separadas
•
•
•
•
Assuma que existem duas threads diferentes rodando o método
transfer().
Ambos farão o seguinte:
– Lerão o saldo da conta original e copiarão para um registrador.
– Diminuirão o valor a transferir da conta original
– Copiarão o valor do registrador para a conta original.
– Os mesmos passos se darão com a conta de destino do
dinheiro.
Duas threads ocorrendo ao mesmo tempo podem se interromper
em qualquer ponto.
Se uma interromper a outra no meio de um crédito ou de um
débito, o saldo final pode ficar corrompido.
Cocorrência em Threads
11
Fazendo com que os métodos sejam atômicos
• Usamos a palavra chave synchronized para declarar que um
método acessa informação crítica e como tal não deve ser
interrompido.
public synchronized void transfer(int from, int to,
int amount) throws InterruptedException
if (accounts[from] < amount) return;
accounts[from] -= amount;
accounts[to] += amount;
ntransacts++;
if (ntransacts % NTEST == 0) test();
}
Cocorrência em Threads
{
12
Locks de objetos
•
Cada objeto Java tem um lock (tranca) que pode
ser obtido por uma thread.
• Uma thread tem que esperar se tentar obter um lock em um
objeto já trancado.
• É mais ou menos como uma fila para usar um banheiro que tenha
apenas um reservado.
• Entretanto, se há mais de um reservado, então podemos ter duas
pessoas usando-os simultaneamente.
• Logo, se há dois objetos de lock, cada um pode ser usado sem
preocupação com o outro.
Cocorrência em Threads
13
Fazendo com que os métodos sejam atômicos
• Quando uma thread manda uma mensagem sincronizada para um
objeto e nenhuma outra thread está executando um método
sincronizado naquele objeto, o que acontece?
• O Java “tranca” aquele objeto e outros métodos sincronizados não
podem acessar aquele objeto até que o primeiro termine.
• É como se uma pessoa quisesse ir ao banheiro e não tivesse
ninguém no reservado  ela entra e tranca a porta.
• Agora ninguém mais pode entrar no reservado até que a pessoa
destranque a porta.
Cocorrência em Threads
14
E se a pessoa passa mal dentro do reservado?
• Um problema possível com uma situação destas é que uma pessoa
pode ter um ataque cardíaco e morrer dentro do banheiro.
• Como é que fazemos para que outras possam também usar o
banheiro?
• O mesmo acontece com threads: se uma thread que tem um lock de
um objeto entrar em loop eterno, temos problemas sérios...
Cocorrência em Threads
15
Outro problema : deadlock
synchronized(Foo) {
synchronized(Bar) {
/* Deadlocked */
}
}
Está com o lock de Foo e
espera que outro libere o
lock de Bar.
synchronized(Bar) {
synchronized(Foo) {
/* Deadlocked */
}
}
Está com o lock de Bar e
espera que outro libere o
lock de Foo.
Regra : sempre adquira os locks na mesma ordem...
Cocorrência em Threads
16
Blocos sincronizados
• A palavra chave synchronized não se restringe a métodos.
• Podemos sincronizar blocos de código sobre objetos que são
compartilhados.
• Assim, só o trecho dentro do bloco é devidamente sincronizado. O
resto do método não.
• O código ficaria assim:
public void run() {
:
synchronized (objeto) {
//Ações a serem tomadas com o objeto
}
:
}
Cocorrência em Threads
17
O que queremos dizer é...
Object mutexLock = new Object();
:
public void someMethod() {
SeçãoNãoCrítica;
synchronized(mutexLock) {
SeçãoCrítica;
}
SeçãoNãoCrítica2;
}
Para que eu preciso sincronizar o método inteiro
se só aquela seção crítica pode dar problemas?
Cocorrência em Threads
18
Prioridades
• Toda thread tem uma prioridade variando de 1 a 10.
• Existem constantes dentro da classe Thread para coordenar isto
– MAX_PRIORITY
– MIN_PRIORITY
– NORM_PRIORITY
• Para definir a prioridade de uma thread, use o método setPriority.
• É trabalho do Scheduler manter as threads de prioridade mais altas
rodando.
Cocorrência em Threads
19
Probleminha...
• Nem todo sistema operacional tem 10 níveis distintos de prioridade.
• O Windows, por exemplo, tem apenas 7.
• Assim, uma thread de prioridade n pode acabar tendo os mesmos
privilégios (ou até menos) que outra de prioridade n-1, se ambas as
prioridades forem mapeadas para um mesmo valor pela JVM.
Cocorrência em Threads
20
Especificação
• Extraído de The Java Language Specification
Every thread has a priority. When there is competition
for processing resources, threads with higher priority
are generally executed in preference to threads with
lower priority. Such preference is not, however, a
guarantee that the highest priority thread will always
be running, and thread priorities cannot be used to
reliably implement mutual exclusion.
• Moral da história : o escalonamento de threads por prioridades é
altamente dependente da implementação da JVM e não devem os
confiar muito nele...
Cocorrência em Threads
21
Nota da tradução...
• Repare que a especificação da linguagem não menciona nada
quanto a threads de mesma prioridade rodando ao mesmo tempo...
• Isto é, o implementador da JVM tem grande liberdade neste tópico.
• O Windows implementa timeslices.
• Já o Solaris deixa as threads de rodando até que elas morram ou
entrem em estado idle (ao pedir um recurso travado ou executando
algum comando que as interrompa como wait(), yield(), etc.)
Cocorrência em Threads
22
Starvation
• Ocorre quando não temos um escalonador justo.
• As threads de mais altas prioridades consomem todos os recursos e
impedem que threas de menos prioridade executem.
• Isto é chamado starvation
• Pode acontecer, dependendo do SO e da implementação do Java
Cocorrência em Threads
23
Threads Egoístas
• Uma thread egoísta é aquela que quando toma conta da máquina,
não quer sair nunca mais.
• Neste caso, só o SO pode retirá-la da máquina.
• Não é boa prática de programação fazer threads egoístas.
• Solução: usar os comandos sleep() e yield().
Cocorrência em Threads
24
O método sleep()
• O método sleep coloca o processo para dormir por um número de
milissegundos definido pelo seu parâmetro.
public void run() {
for(;;) {
try {
sleep(1000); // Pause for 1 second
} catch (InterruptedException e) {
return;
// caused by thread.interrupt()
}
System.out.println(“Tick”);
}
}
Cocorrência em Threads
25
O método sleep()
public void run() {
for(;;) {
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
System.out.println(“Tick”);
}
}
Esta thread não necessariamente imprime a palavra “Tick” uma vez por segundo.
Afinal, ela entra em estado idle e não necessariamente é escalonada
imediatamente após “acordar do seu sono”.
O tempo do sleep é um limite inferior para o tempo entre impressões.
Cocorrência em Threads
26
Interrompendo uma thread
• Quando o método run() termina, a thread se encerra (entra em
estado dead e espera que o Garbage Collector a elimine da
memória).
• Neste caso, está tudo bem.
• Entretanto, outros podem pedir que a thread termine.
• Para fazê-lo, devemos usar o método interrupt() da thread.
Cocorrência em Threads
27
Interrompendo uma thread
• Logo, o código da nosso método run() deveria ter o seguinte
formato:
public void run() {
:
while((!interrupted())&&(outras condições) {
//faça o trabalho
}
:
}
• O método interrupted() é estático enquanto que o
isInterrupted() pertence ao objeto e não tem efeitos
colaterais.
• O problema é que esta thread é uma thread egoísta!!!!
Cocorrência em Threads
28
Interrompendo o sono de uma Thread
• Quando uma thread está em modo sleep e sofre uma interrupção,
uma exceção da classe InterruptedException acontece.
• Logo, devemos tratá-la!!!
• Podemos ignorar o pedido de interrupção, se a thread for importante
o suficiente ou podemos preparar-nos para sair.
• Nota: a interrupção só ocorre se estávamos dormindo. Caso
contrário, temos que checar os flags de interrupção.
Cocorrência em Threads
29
Interrompendo o sono de uma Thread
• O código para fazer isto é:
public void run() {
:
try {
while(condições) {
//faça o trabalho
:
sleep(n)
:
//mais trabalho
}
} catch (InterruptedException e) {
//A thread foi interrompida durante o sono
}
finally {
//Ações de limpeza e término do loop
}
:
}
Cocorrência em Threads
30
Sendo bonzinho...
• Uma maneira de uma thread ser gentil, é usar o método yield().
• Usando este método, ela sai do controle da máquina e diz para a
JVM escalonar outra thread em seu lugar.
• O problema é que se a thread boazinha é a da mais alta prioridade,
certas implementações da JVM pode escaloná-la novamente.
• É melhor chamar o método sleep(). Assim garantimos que a
thread atual não será escalonada imediatamente...
Cocorrência em Threads
31
Esperando uma condição
• Imagine que você deseja esperar por uma condição antes de
continuar.
• Se você fizer um loop infinito, você pode fazer o sistema entrar em
deadlock.
while (!condition) {}
• Usar o comando yield evita o deadlock, mas é muito ineficiente.
while (!condition) {yield()}
Cocorrência em Threads
32
Esperando uma condição : wait e notify
• wait() é parecido com yield(), mas necessita que alguém o acorde.
:
while (!condition) wait();
//Agora a cond. foi satisfeita
:
• A thread que pode afetar a condição de espera chama o método
notify() para acordar uma das threads que foi dormir.
• Não necessariamente a thread que acorda é aquela que queremos,
• O programador precisa se assegurar que para cada chamada de
wait() existe uma chamada correspondente de notify()
Cocorrência em Threads
33
Outra solução
• O método notifyAll() habilita todas as threads que estão em
estado de wait.
• Uma delas será escalonada e aproveitará da condição satisfeita.
• As outras, quando forem escalonadas, não terão a condição
satisfeita e voltarão a dormir...
Cocorrência em Threads
34
Produtor Consumidor
• O problema do produtor-consumidor é um caso clássico de uma
thread que deve esperar por uma condição a ser satisfeita por
outras threads.
• No nosso caso abaixo, o Homer só pode comer biscoitos depois
que a Marge os fizer!!!
Cocorrência em Threads
35
Marge & Homer
• A Marge só assa biscoitos quando precisa, pois a jarra é de tamanho
limitado.
• O Homer só os come quando eles existem.
• Solução:
– Marge faz biscoitos, avisa para o Homer que estão prontos e
espera.
– Homer come os biscoitos, avisa para a Marge que eles acabaram
e espera.
Cocorrência em Threads
36
Produtor e Consumidor
Consumidor
synchronized (lock) {
while (!resourceAvailable()) {
lock.wait();
}
consumeResource();
}
Produtor
produceResource();
synchronized (lock) {
lock.notifyAll();
}
Quando o produtor executa, ele tira do estado de wait todos os
objetos consumidores que estão esperando sobre o objeto lock
Cocorrência em Threads
37
Seqüência de execução Wait/Notify
1. synchronized(lock){
2. lock.wait();
9. consumeResource();
10. }
Thread
Consumidor
Objeto Lock
7. Readquire lock
8. Retorna do wait()
Cocorrência em Threads
3. produceResource()
4. synchronized(lock) {
5. lock.notify();
6.}
Thread
Produtor
38
A classe JarraBiscoitos
public class CookyJar {
private int contents;
private boolean available = false;
public synchronized void getCooky(String who) {
while (!available) {
try {wait();} catch (InterruptedException e) { }
}
available = false;
notifyAll();
System.out.println( who + " ate cooky " + contents);
}
public synchronized void putCooky(String who, int value) {
while (available) {
try {wait();} catch (InterruptedException e) { }
}
contents = value;
available = true;
System.out.println(who + " put cooky " + contents +
" in the jar");
notifyAll();
}
}
Cocorrência em Threads
39
A classe Homer
public class Homer implements Runnable {
CookyJar jar;
public Homer(CookyJar jar) {
this.jar = jar;
}
public void eat() {
jar.getCooky("Homer");
try {
Thread.sleep((int)Math.random() * 1000);
} catch (InterruptedException ie) {}
}
public void run() {
for (int i = 1 ; i <= 10 ; i++) eat();
}
}
Cocorrência em Threads
40
A classe Marge
public class Marge implements Runnable {
CookyJar jar;
public Marge(CookyJar jar) {
this.jar = jar;
}
public void bake(int cookyNumber) {
jar.putCooky("Marge", cookyNumber);
try {
Thread.sleep((int)Math.random() * 500);
} catch (InterruptedException ie) {}
}
public void run() {
for (int i = 0 ; i < 10 ; i++) bake(i);
}
}
Cocorrência em Threads
41
O testador das classes
public class SimpsonsTest {
public static void main(String[] args) {
CookyJar jar = new CookyJar();
Homer homer = new Homer(jar);
Marge marge = new Marge(jar);
new Thread(homer).start();
new Thread(marge).start();
}
}
Cocorrência em Threads
42
Semáforos
• Os sistemas operacionais normalmente oferecem semáforos como
oeprações primitivas.
• Java não oferece mecanismos de semáforos, mas nós podemos criálos facilmente usando o mecanismo de sincronização.
Cocorrência em Threads
43
Semáforos em Java
public class Semaphore {
private int value;
public Semaphore() {value = 0;}
public Semaphore(int value) {this.value = value;}
public synchronized void acquire() {
while (value == 0)
try {wait();}
catch (InterruptedException ie) { }
value--;
}
public synchronized void release() {
++value;
notify();
}
}
Cocorrência em Threads
44
Moral da história : estados possíveis de uma Thread
born
start()
blocked
I/O
completed
ready
yield()
waiting
wait()
running
sleep()
sleeping
terminate
dead
Cocorrência em Threads
45
Download

Concorrência em Threads - Algoritmos Genéticos, por Ricardo Linden