Programação Concorrente

Propaganda
Programação Concorrente JAVA
Prof. Alexandre Monteiro
Recife
‹#›
Contatos

Prof. Guilherme Alexandre Monteiro Reinaldo

Apelido: Alexandre Cordel

E-mail/gtalk: [email protected]
[email protected]

Site: http://www.alexandrecordel.com.br/fbv

Celular: (81) 9801-1878
Conteúdo

O que é

Motivação

Conceitos

Processos

Threads

Propriedades

Safety

Liveness

Threads em JAVA
Programação Concorrente
[ O que é ]

“Um programa concorrente é um
conjunto de programas seqüenciais
comuns que são executados em um
paralelismo abstrato” (M.Bem-Ari)
Programação Concorrente
[ O que é ]

“Um programa concorrente especifica 2
ou mais processos que cooperam para
realizar uma tarefa. Processos cooperam
através de comunicação; utilizam
variáveis compartilhadas ou troca de
mensagens“(G. R. Andrews)
Programação Concorrente
[ Motivação ]

Aproveitar hardware com múltiplos processadores

Atender a vários usuários simultaneamente

Melhorar o desempenho das aplicações

Aumentar a disponibilidade da CPU para o usuário

Objetos ativos e controle de atividades

Programas paralelos
Programação Concorrente
[ Conceitos ]

Paralelismo
• Processamento simultâneo físico

Concorrência
• Processamento simultâneo lógico (aparente)
• Requer entrelaçamento (interleaving) de ações

Processo
• Execução de um programa (running)

Programa Concorrente
• Vários processos que cooperam para a realização de
uma tarefa ou mais tarefas
Programação Concorrente
[ Conceitos ]

Comunicação
• Variáveis compartilhadas
• Passagem de mensagens

Sincronização
• Exclusão mútua de seções críticas
• Sincronização por condição

Estado de um programa concorrente
• Consiste dos valores das variáveis (explícitas e implícitas)
• A execução de um comando muda o estado

Ações atômicas
• Transformação indivisível de estado
Programação Concorrente
[ Processos ]





Um processo é um programa que está em algum
estado de execução
Tem espaço de endereçamento próprio, que é
mapeado pelo S.O. para memória física
Possui um fluxo de controle ou thread único
Mantém um contador de programa (PC) que indica
o endereço da próxima instrução
A MMU (Memory Management Unit) traduz os
endereços lógicos em endereços físicos, que
normalmente não são contíguos (Memória Virtual)
Programação Concorrente
[ Processos ]

Espaço de Endereçamento Lógico
Pilha
Espaço de
Endereçamento
Lógico de um
Processo
Heap
Dados Globais
Instruções
Programação Concorrente
[ Processos ]

Tabela de Processos
•Estado do processo
•Valores dos registradores
•Arquivos abertos
•Alocação de memória
•PID (Process ID)
•UID (User ID)
•GID (Owner’s Group ID)
Programação Concorrente
[ Processos ]

Estados de um processo
1. Iniciado (Start): processo criado
2. Executando (Running): Utilizando a CPU
3. Executável ou Pronto (Runnable ou Ready):
Esperando para ser escalonado para usar a CPU
4. Suspenso (Suspended): Recebeu um sinal para ser
suspenso
5. Bloqueado (Blocked): Esperando pela conclusão de
algum serviço solicitado ao S.O.
6. Encerrado (Dead): processo morto
Programação Concorrente
[ Processos ]
Ativo
Bloqueado
Executável
Executando
Encerrado
Iniciado
Suspenso
Programação Concorrente
[ Threads ]




Um processo pode ter mais de uma Thread
(Linha)
Cada Thread possui contador de programa e
pilha próprios
Quando um processo é escalonado para ser
executado, uma das Threads entra em
execução
As Threads compartilham as variáveis globais
do processo
Programação Concorrente
[ Threads ]
Espaço de Endereçamento de um Processo
Pilha
Thread 1
Thread 2
Thread n
Pilha
Pilha
Pilha
Contador
de Programa
Contador
de Programa
Contador
de Programa
Heap
Variáveis Globais
Instruções
Programação Concorrente
[ Threads ]

Vantagens sobre processos compartilhando memória
•São muito mais leves de serem criadas
•A troca de contexto é mais suave pois
compartilha instruções, heap e
variáveis globais
•Facilitam o compartilhamento de
memória
Threads
[ Ciclo de Vida ]
Intervalo de tempo expirou
4.sleep()
Dormindo
6.stop()
1.new Thread()
escalonada
Criada
Encerrada
Término
3.run()
2.start()
Pronta
Executando
interrompida
Operação
de E/S
concluída
5.notify()
5.notityAll()
4.wait()
Esperando
Bloqueada
Operação
de E/S
iniciada
Threads
[ Sincronização ]

Sincronizando Threads
•Programação com múltiplas Threads requer
bastante cuidado:
- Acesso / Atualização de variáveis compartilhadas
- Starvation (processo nunca é executado)
- Deadlock (impasse e dois ou mais processos ficam impedidos
de continuar suas execuções e ficam bloqueados)
- Acesso a estados inválidos de outros objetos
Threads
[ Sincronização ]


A sincronização baseia-se na idéia de que para
acessar um método sincronizado ou um entrar em
um bloco sincronizado, é preciso obter (ou já ter)
o lock desse objeto.
A Thread que conseguir esse lock é a única
autorizada a acessar os recursos protegidos
através de sincronização.
Programação Concorrente
[ Propriedades ]






Safety: O programa nunca entra em um estado
inconsistente
Liveness: Em algum momento o programa entra
em um estado consistente
Correção Parcial: Se o programa terminar, o
resultado está correto. Caso contrário, nunca
pode dar o resultado correto
Término: O programa termina eventualmente
Ausência de Deadlock: Nunca todos os processos
estarão bloqueados
Correção Total: O programa sempre termina e
produz o resultado correto
Safety
[ Introdução ]


Objetos interagindo em múltiplas Threads normalmente
provocam interferência
Programas concorrentes devem possuir 2 propriedades:
•Safety: Nada de mau acontecerá durante a
execução do programa.
•Liveness: Algo de bom acontecerá durante a
execução do programa.
Safety
[ Imutabilidade ]

Variáveis de instância constantes (final em Java)

Encapsulamento

Não requer sincronização

Classes sem estado (stateless)
•Como não há estado, não há interferência
Safety
[ Sincronização ]


Um Objeto/variável sempre está pronto para sofrer
modificações, mesmo quando ainda está no meio do
processamento de alguma
Acesso a estados inconsistentes devem ser evitados:
•Conflitos de leitura/escrita: vários lêem
enquanto um escreve
•Conflitos de escrita/escrita: vários lêem e
escrevem ao mesmo tempo
Safety
[ Sincronização ]

No contexto de OO, podemos ter:
•Sincronização Total
- Objetos totalmente sincronizados. Podem
fazer apenas uma operações por vez
•Sincronização Parcial
- Parte do objeto é sincronizado. Somente
métodos sincronizados são travados
Safety
[ Contenção ]


Baseia-se na idéia de manter referências únicas para
objetos internos isolados dos demais.
São acessados apenas através de métodos
sincronizados do objeto no qual estão contidos,
estando, portanto, protegidos.
Safety
[ Contenção ]
•É preciso definir um protocolo que garanta a
exclusividade do recurso
null
null
serviço 1
vizinho
vizinho
serviço 0
serviço 2
vizinho
vizinho
serviço 3
null
Recurso
Liveness
[ Falhas de Liveness ]



São tão sérias quanto falhas de Safety
São mais difíceis de identificar e evitar durante o
projeto
Tipos de falhas
•Contenção: uma thread, apesar de estar
pronta para executar, não executa porque
outra tomou recursos (também conhecida
como starvation ou adiamento infinito)
Liveness
[ Falhas de Liveness ]
•Dormência: uma thread não pronta falha ao
tentar passar para esse estado.
•Deadlock: duas ou mais threads bloqueiamse em um ciclo vicioso enquanto tentam
acessar travas sincronizadas necessárias
para continuar suas atividades
•Término prematuro: uma thread é parada
ou encerrada prematuramente
Threads JAVA
‹#›
Definições Básicas




Threads são sub-procesos no sistema
operacional.
É menos custoso gerenciar threads do que
processos.
As linguagens Java e Ada possuem
funcionalidades MULTITHREADING na própria
estrutura da linguagem.
C e C++ necessitam de biblioteca especifica
para processamento MULTITHREADING
• Posix p_thread
Diagrama de Estados Thread Java
O diagrama mostra os estados de uma thread de Java pode estar e alguns
métodos que podem ser usados para mudar de um estado para outro.
New Thread

Inicialização do thread - feita através do construtor
Thread().
class MyThreadClass extends Thread{
...
}
...
MyThreadClass myThread = new MyThreadClass();


Neste estado, nenhum recurso do sistema foi alocado para o
thread ainda, assim, a partir daqui, tudo que você pode
fazer é um start(), para ativar o thread, ou um stop(), para
"matá-lo".
A chamada de qualquer outro método não faz sentido e
levantará a exceção IllegalThreadStateException.
Runnable


Este é o estado em que o thread está pronto para rodar. O método start()
requisita os recursos do sistema necessários para rodar o thread e chama
o seu método run().
O método run() é a "alma" de um thread; é neste método que definimos o
que o thread vai executar.
Thread myThread = new MyThreadClass();
myThread.start();


Falamos em "Runnable", ao invés de "Running", porque o thread pode não
estar realmente sendo executado. Imagine um computador com um único
processador - seria impossível executar todos os threads "Runnable" ao
mesmo tempo. O que ocorre é que a CPU deve ser escalonada entre os
vários threads.
Quando um thread está "Running", ele está também "Runnable", e, as
instruções do seu método run() é que estão sendo executadas pela CPU.
Not Runnable

O estado "Not Runnable" significa que o thread está impedido
de executar por alguma razão. Existem 4 maneiras através
das quais um thread ir para o estado "Not Runnable":
•Alguém manda-lhe a mensagem suspend().
•Alguém manda-lhe a mensagem sleep().
•O thread bloqueia, esperando por I/O.
•O thread usa seu método wait() para esperar por uma
variável de condição.
Not Runnable

O exemplo abaixo coloca “myThread” para dormir por 10
segundos...
Thread myThread = new MyThreadClass();
myThread.start();
try {
myThread.sleep(10000);
} catch (InterruptedException e){
}
Not Runnable

Cada uma destas maneiras tem a sua forma específica de sair do estado
"Not Runnable".
•Se o thread foi suspenso, alguém precisa mandar-lhe a
mensagem resume().
•Se o thread foi posto para dormir, ele voltará a ser
"Runnable" quando o número de milisegundos
determinado passar.
•Se o thread está bloqueado, esperando por I/O, a I/O
precisa ser completada.
•Se o thread está esperando por uma variável de
condição, o objeto que a "segura" precisa liberá-la,
através de um notify() ou de um notifyAll()
Dead

Um thread pode morrer de "causas naturais" (quando o seu
método run() acaba normalmente) ou pode ser morto
(AssAssINAdO pelo método stop()).
Thread myThread = new MyThreadClass();
myThread.start();
public void run() {
int i = 0;
while (i < 100) {
i++;
System.out.println("i = " + i);
}
}

Este thread vai morrer naturalmente quando o loop do run()
acabar.
Dead

O que vai acontecer com este thread?
Thread myThread = new MyThreadClass();
myThread.start();
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e){
}
myThread.stop();
Método yield()

O método yield()

Cede a CPU para outros threads
Thread em Java

Em Java, threads são implementadas como uma CLASSE
•Pacote java.lang.Thread
•É uma extensão da classe Thread
•Contrutores:
- public Thread (String nome_da_thread);
- public Thread ( ); // o nome sera Thread-#
• Thread-1, Thread-2,…
Principais Métodos
•run(): é o método que executa as
atividades de uma THREAD. Quando este
método finaliza, a THREAD também
termina.
•start(): método que dispara a execução de
uma THREAD. Este método chama o
método run( ) antes de terminar.
•sleep(int x): método que coloca a THREAD
para dormir por x milisegundos.
Principais Métodos
•join( ): método que espera o término da
THREAD para qual foi enviada a mensagem
para ser liberada.
•interrupt( ): método que interrompe a
execução de uma THREAD.
•interrupted( ): método que testa se uma
THREAD está ou não interrompida.
Estados de uma Thread em Java
nascimento
Término do tempo de dormida
start( )
Fim da E/S
notify( )
notifyAll( )
pronta
run( )
Alocar um processador
executando
E/S
wait( )
sleep( )
esperando
dormindo
stop( )
morta
Fim do Método run( )
bloqueada
Prioridade de Thread

Em Java, a prioridade é determinada com um
inteiro entre 1 e 10.

A prioridade padrão é o valor 5.

10 é a maior prioridade e 1 é a menor.

A THREAD herda a prioridade da THREAD que
acriou.

void setPriority(int prioridade);

int getPriority( );
Algoritmo de Escalonamento
Prioridade 10
Prioridade 9
A
B
C
Prioridade 8
.
.
.
Prioridade 3
D
E
Prioridade 2
Prioridade 1
G
F
Exercício 01


O programa cria 04 threads e as coloca para
dormir.
ThreadBasica é uma extensão da classe Thread.
Exercício 01
•Analise como se chama o método sleep().
•Crie n THREADs, onde n é definido pelo
usuário.
•Utilize o método join no main para esperar
as THREADs terminarem.
try {
uma_thread.join( ); //
uma_thread.join(tempo)
…
}catch (InterruptedException e) {
…
}
Escalonamento de Threads
Prioridade 10
Prioridade 9
A
B
C
Prioridade 8
.
.
.
Prioridade 3
D
E
Prioridade 2
Prioridade 1
G
F
Exercício 02


Prioridades de Threads
Utilize o método setPriority(int) para mudar a
prioridade de threads
•Utilize 01 thread com prioridade 1, 01 com
prioridade 09 e as outras com prioridade 05.
•Faça com que uma das threads de alta
prioridade durma por 10 ms antes de
terminar.
•Faça com que outra thread de alta
prioridade faça uma entrada de dado.
Exercício 03

Problema Produtor X Consumidor
- Com buffer de tamanho 1.
- Variáveis compartilhadas.
- A solução do problema seria utilizar-se duas
THREADS: 01 consumidor e 01 produtor.
- O que ocorre se não houver sincronização
entre a leitura e escrita?
Exercício 04



Problema do Produtor X Consumidor com
sincronização do Buffer.
Em Java, a sincronização entre threads é feita
através do conceito de monitores.
Monitor é um agrupamento de funções, cujas
execuções não podem se dar de forma
concorrente.
Exercício 04



Utilizar os métodos multuamente excludentes
de um objeto como do tipo synchronized em
Java.
Utilizar os métodos wait( ) e notify( ) para
bloquear e liberar, respectivamente, as
threads.
Resolver o problema de Produtor-Consumidor.
A interface Runnable

A solução encontrada em Java foi a utilização de uma
interface: Runnable
•No caso, tem-se de implementar esta
interface, que possui o método run( ).
public class Filho extends Pai implements Runnable
• Ao implementar uma interface, a classe se capacita
a ser tratada como se fosse um objeto do tipo da
inteface implementada.
- Se a classe Filho implementar a interface Runnable, ela
pode ser tratada como tal.
A interface Runnable


Para utilizar multithreads em Java é
necessário instanciar um objeto de uma classe
que estende a classe básicaThread, certo?
Uma vez que Java não possui herança
múltipla, como eu posso utilizar um objeto,
cuja classe já é derivada, como no caso da
ClasseThread?
public class Filho extends Pai extends Thread {
……………….
} // isto nao eh possivel em Java
A interface Runnable

Cria-se uma thread (Classe Thread), passando para o seu
construtor uma referência do objeto que implementa a
interface Runnable.
Thread uma_Thread = new Thread(Runnable obj_thread)
Thread uma_Thread = new Thread(Runnable obj_thread, String nome_da_thread)
Exercício 5



Crie um programa onde o usuário possa escolher a quantidade de
processos a serem criados para execução em concorrência.
Estes devem ser nomeados e atribuída prioridades valores de 1
até um valor máximo 10, passado para cada um deles.
Quando o processo atingir o número correspondente a metade do
valor máximo do número de processos, deve entrar em espera de
um tempo calculado de maneira randômica entre 1 e 5 segundos
e posteriormente ser liberado.
O programa deve ter um menu com as seguintes opções:
1) Criação de processos (qtd e nomes)
2) Prioridades dos processos (int de 1 a 10)
Exercício 6



Veja a classe a seguir
É uma classe que implementa Runnable e, no método run(),
apenas imprime dez mil números.
Vamos usá-las duas vezes para criar duas threads e imprimir os
números duas vezes simultaneamente:
Exercício 6
Exercício 6




Se rodarmos esse programa, qual será a saída? De um a mil e
depois de um a mil? Provavelmente não, senão seria sequencial.
Ele imprimirá 0 de t1, 0 de t2, 1 de t1, 1 de t2, 2 de t1, 2 de t2 e
etc? Exatamente intercalado?
Na verdade, não sabemos exatamente qual é a saída.
Rode o programa várias vezes e observe: em cada execução a
saída é um pouco diferente.
Depois mude a prioridade e veja como ocorre a execução.
Exercício 6



O problema é que no computador existe apenas um processador
capaz de executar coisas. E quando queremos executar várias
coisas ao mesmo tempo, e o processador só consegue fazer uma
coisa de cada vez? Entra em cena o escalonador de threads.
O escalonador (scheduler), sabendo que apenas uma coisa pode
ser executada de cada vez, pega todas as threads que precisam
ser executadas e faz o processador ficar alternando a execução
de cada uma delas.
A ideia é executar um pouco de cada thread e fazer essa troca
tão rapidamente que a impressão que fica é que as coisas estão
sendo feitas ao mesmo tempo.
Exercício 6


O escalonador é responsável por escolher qual a próxima thread a ser
executada e fazer a troca de contexto (context switch). Ele
primeiro salva o estado da execução da thread atual para depois
poder retomar a execução da mesma. Aí ele restaura o estado da
thread que vai ser executada e faz o processador continuar a
execução desta. Depois de um certo tempo, esta thread é tirada do
processador, seu estado (o contexto) é salvo e outra thread é
colocada em execução. A troca de contexto é justamente as
operações de salvar o contexto da thread atual e restaurar o da
thread que vai ser executada em seguida.
Quando fazer a troca de contexto, por quanto tempo a thread vai
rodar e qual vai ser a próxima thread a ser executada, são escolhas
do escalonador. Nós não controlamos essas escolhas (embora
possamos dar "dicas" ao escalonador). Por isso que nunca sabemos ao
certo a ordem em que programas paralelos são executados.
Exercício 6

Todo esse processo é feito automaticamente pelo escalonador do
Java (e, mais amplamente, pelo escalonador do sistema
operacional). Para nós, programadores das threads, é como se as
coisas estivessem sendo executadas ao mesmo tempo.
Referências


Concurrent Programming - Hartley (capítulo 1)
Concurrent Programming in Java - Lea
(capítulo 1, 2 e 3)
Programação Concorrente JAVA
Prof. Alexandre Monteiro
Recife
‹#›
Download