0% acharam este documento útil (0 voto)
3 visualizações75 páginas

Fundamentos de Threads em Java

O documento aborda a programação com threads em Java, detalhando a criação e gerenciamento de threads usando a classe Thread e a interface Runnable. Ele explica conceitos como a interrupção de threads, o uso de métodos sincronizados para evitar condições de corrida e a implementação de tarefas com classes anônimas e expressões lambda. Além disso, inclui exercícios práticos para reforçar o aprendizado sobre a manipulação de threads.

Enviado por

Joseph Donald
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
3 visualizações75 páginas

Fundamentos de Threads em Java

O documento aborda a programação com threads em Java, detalhando a criação e gerenciamento de threads usando a classe Thread e a interface Runnable. Ele explica conceitos como a interrupção de threads, o uso de métodos sincronizados para evitar condições de corrida e a implementação de tarefas com classes anônimas e expressões lambda. Além disso, inclui exercícios práticos para reforçar o aprendizado sobre a manipulação de threads.

Enviado por

Joseph Donald
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

11

Threads

Helder da Rocha
helder@[Link]
Conteúdo
[Link]ção [Link]
[Link] [Link]ções
[Link]ões [Link]
[Link] e Strings [Link]
[Link] [Link]ção funcional
[Link] [Link] e I/O
[Link]ções [Link] de dados
2
Estrutura
• Este módulo tem duas partes
• Parte 1 - fundamentos de threads em Java
(programação em baixo nível usando classe Thread e
interface Runnable)
• Parte 2 - API de concorrência (programação em nível de
abstração mais algo usando classes e interfaces do
pacote [Link])

3
Threads em Java
• Representados pela classe [Link].
• Qualquer programa em Java possui pelo menos um thread,
que executa as instruções do método main().
• O thread principal é chamado de “main”.
Thread principal = [Link]();

[Link]("Nome do thread: "


+ [Link]()); // imprime main

[Link]("Thread toString(): "


+ principal); // imprime [main, 5, main]
4
A interface Runnable
• Todo thread precisa de uma sequência de instruções para
executar
– O thread main executa automaticamente o conteúdo do
método static void main(String[]) disparado pela JVM
– Threads adicionais executam automaticamente o
conteúdo do método void run() de uma classe que
implementa [Link]

5
Implementando Runnable
• A interface [Link] é uma interface funcional
public interface Runnable {
void run();
}
• Uma implementação de Runnable que imprime uma linha de texto
e outra com o nome do thread que está executando as instruções.
public class RunnableHelloWorld implements Runnable {
@Override public void run() {
[Link]("Hello world paralelo!");
[Link]("Eu sou o thread: "
+ [Link]().getName());
}
}

• Threads recebem nomes e IDs automaticamente.


6
Rodando Runnable no mesmo thread
• Um objeto Runnable é um objeto Java qualquer
• O programa abaixo executa o método run() no thread principal (e
imprime também o nome deste thread):
public class ThreadExampleSync {
public static void main(String[] args) {
Runnable paralelo = new RunnableHelloWorld();
[Link]();
[Link]("Thread principal: " +
[Link]().getName());
}
}
Hello world paralelo!
Executando no thread main Eu sou o thread: main
Thread principal: main
7
Como iniciar um novo Thread
• Para criar um novo Thread é preciso criar uma nova
instância da classe Thread, que recebe como argumento
uma implementação de Runnable:
Runnable tarefa = new ImplementacaoDeRunnable();
Thread t = new Thread(tarefa);

• Para iniciar (executar) o Thread e executar o conteúdo de


run() através desse novo Thread chame o método start():
[Link]();

8
Executando Runnable em novo Thread
• O programa abaixo cria um novo Thread com a tarefa
Runnable e depois o inicia com o método start():
public class ThreadExampleAsync {
public static void main(String[] args) {
Runnable paralelo = new RunnableHelloWorld();
Thread t1 = new Thread(paralelo);
[Link]();
[Link]("Thread principal: " +
[Link]().getName());
}
}
Thread principal: main
Hello world paralelo!
Executando no thread Thread-0 Eu sou o thread: Thread-0
9
Executando Runnable em novo Thread
JVM

Thread: main

main() {
Runnable task = new RunnableHelloWorld();
Thread t1 = new Thread(task);
Thread: Thread-0
[Link]();
[Link]("Fim"); run() {
} for(int i = 0; i < 10000; i++)
[Link](i);
main is done
}

Thread-0 is done
JVM is done

10
Tarefa como classe anônima
• Se a tarefa Runnable será usada apenas uma vez, pode
ser implementada como classe anônima
public class ThreadExampleAsync3 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
[Link]("Hello world paralelo!");
}
});
[Link]();
[Link]("Thread principal: "
+ [Link]().getName());
}
} 11
Tarefa como expressão lambda
• Desde Java 8 é possível (e muito comum) implementar
Runnable como método anônimo (expressão lambda)

public class ThreadExampleAsync4 {


public static void main(String[] args) {
Thread t1 = new Thread(
() -> [Link]("Hello world paralelo!") );
[Link]();
[Link]("Thread principal: "
+ [Link]().getName());
}
}

12
Exercícios
• 1. Crie uma implementação de Runnable que receba dois
argumentos
• um nome (String)
• um intervalo de tempo em milissegundos (long)
• O método deve ser impresso para que a String seja
impressa periodicamente na tela, esperando um tempo fixo
correspondente ao intervalo recebido
• Escreva um método main() para testar a aplicação com 3
threads, com textos diferentes e intervalos variando entre
500 e 2000 ms.
13
Interrupção de threads
• Um thread só termina (normalmente) quando run() terminar.
• Métodos de interrupção não interrompem o thread. Ligam flag
(INTERRUPT) que deve ser usado para finalizá-lo (fazer run() terminar
normalmente).
• Métodos de instância:
– void interrupt() – liga o flag INTERRUPT.
– boolean isInterrupted() – retorna true se INTERRUPT é true.
• Método estático (atua sobre [Link]()):
– static boolean interrupted() – retorna true se INTERRUPT
for true e em seguida muda INTERRUPT para false.

14
Thread que termina com interrupt()
public class InterruptRunnable implements Runnable {
@Override public void run() {
Repete enquanto
boolean interrupt = false; interrupt for false
while(!interrupt) {
interrupt = [Link]();
[Link](">INTERRUPT flag: " + interrupt);
}
[Link]("INTERRUPTED flag: " + interrupt);
[Link]("Thread "
+ [Link]().getName() + " is DONE!");
}
}

15
Pondo threads para dormir
• O thread que está executando pode suspender sua execução
por um determinado tempo chamando
[Link](milissegundos)
• Para dormir 1 segundo (1000 milissegundos):
[Link](1000);
• Nesse intervalo, outros threads que estejam esperando acesso
à CPU terão oportunidade de executar, independente de sua
prioridade.
• O método sleep() precisa lidar com InterruptedException

16
InterruptedException
• Será lançada se a flag INTERRUPT estiver ativada em um
thread durante a execução de sleep()
• É checked exception: precisa ser capturada ou declarada
• Isto pode ser usado para finalizar um thread, sem a
necessidade de testar o flag INTERRUPT
• Bloco try-catch pode capturar a exceção e finalizar o thread
normalmente
• Bloco try-catch pode ignorar a exceção e a interrupção (a
exceção automaticamente muda o estado do INTERRUPT
para false)
17
public class InterruptSleepExample {

Finalização com public static void main(String[] args) {


Thread t1 =
new Thread(new RandomLetters());

InterruptedException [Link]();
// main thread executa suas tarefas
[Link](); // sets interrupt in t1
class RandomLetters implements Runnable { [Link]("main is DONE!");
@Override public void run() { }
try { }
while(true) {
[Link](" " + (char)('A' + new Random().nextInt(26)));
[Link](200);
}
} catch (InterruptedException e) {
[Link]("\n" + [Link]().getName() + " interrupted.");
[Link]("INTERRUPTED flag: " + [Link]().isInterrupted());
}
[Link]("Thread " + [Link]().getName() + " is DONE!");
}
} 18
Religando o flag de interrupção
• Se o código decidir não interromper o thread, outra parte do programa
poderá assumir essa responsabilidade (é preciso religar o flag)
public void run() {
while(true) { // este loop continua mesmo com interrupção
try {
// chamadas externas que poderão lidar com o INTERRUPT
[Link](100); InterruptedException
} catch(InterruptedException e) { desliga flag de interrupção
[Link]("Thread interrompido que não será finalizado."));
[Link]().interrupt(); // IMPORTANTE!
}
} Liga novamente o flag
[Link]("Thread finalizado.")); de interrupção
} 19
Esperando threads terminarem
• O método join() faz um thread esperar que o outro termine
– Se um t1 chamar [Link](), t1 é suspenso até t2 terminar
• Como todo método que suspende threads, join() lança
InterruptedException (precisa ser capturada ou declarada)
Thread t1 = new Thread(() -> {
for(int i = 0; i < 10000; i++) [Link](i);
});
[Link]();
[Link]("Waiting for " + [Link]());
[Link]();
[Link]("Thread main is DONE!");
20
Esperando threads terminarem
JVM

Thread: main

main() {
Runnable task = new RunnableLoop();
Thread t1 = new Thread(task);
Thread: Thread-1
[Link]();
[Link]();
run() {
for(int i = 0; i < 10000; i++)
WAITING
[Link](i);
for
}
Thread-1
}
Thread-1 is done

main is done
JVM is done
21
Exercícios
• 2. Implemente um mecanismo de interrupção no método run() do
exercício anterior. Use o main ou outro thread para interromper cada
thread depois de um determinado tempo.
• Ex: faça o thread principal dormir por três intervalos de 5
segundos, interrompendo um dos threads após cada intervalo.
• 3. Crie duas tarefas Runnable.
• Uma deve calcular a raiz quadrada ([Link]) de todos os
números ímpares de 1 a 99 e imprimir os resultados na tela
esperando 50ms entre operações.
• A outra deve calcular a raíz cúbica ([Link]) de todos os
números pares de 2 a 1000, e imprimir cada décimo resultado na
tela, esperando 10ms entre operações.
• As tarefas devem executar simultaneamente em threads distintos.
Depois que ambas terminarem, o thread main deve imprimir "Fim".
22
Condições de corrida (race conditions)
• Acontecem quando não é possível prever o resultado de uma
sequência de instruções acessadas simultaneamente por mais
de um thread
– Dados compartilhados podem ser alterados incorretamente
– Informações podem estar desatualizadas
– Operações não-associativas podem ser executadas fora
de ordem
• É importante garantir que a aplicação continue a funcionar
corretamente quando partes dela executarem em threads
separados.
23
Blocos sincronizados
• Condições de corrida podem ser evitadas protegendo as
instruções através de travas de exclusão mútua (mutex locks)
• Instruções protegidas funcionam como uma operação atômica

• Um bloco synchronized garante a um thread o acesso


exclusivo a um objeto, enquanto executa o conteúdo do bloco,
impedindo que outros threads tenham acesso ao objeto

• O bloco synchronized recebe uma referência para o objeto


Object obj = new Object() {
synchronized( obj ) {
// apenas um thread poderá entrar aqui de cada vez
} 24
Object obj1 = new Object();
Thread-1

Blocos sincronizados
Thread-2 • Dois threads que acessam um
objeto tentam obter acesso ao bloco
synchronized
obj1
synchronized( obj1 )

• Para ter acesso exclusivo ao bloco,


o thread precisa obter a trava
(permissão) de acesso do objeto

25
Object obj1 = new Object();
Thread-1

Blocos sincronizados
• O thread que obtiver a trava de
acesso poderá entrar no bloco
obj1 • Outro thread que deseja entrar no
synchronized( obj1 )

bloco é bloqueado e terá que


esperar até que a trava seja
liberada
Thread-2

26
Object obj1 = new Object(); Blocos sincronizados
Thread-1

• Quando o thread sair do bloco, a


trava é devolvida ao objeto
obj1 • Outros threads que estiverem
synchronized( obj1 )

esperando pela trava podem obtê-


la e serão desbloqueados
Thread-2

27
Object obj1 = new Object(); Blocos sincronizados
Thread-2 • O outro thread obtém a trava e a
retém enquanto estiver executando as
instruções do bloco
obj1
synchronized( obj1 )

• Se outros threads chegarem enquanto


o thread estiver executando, eles
serão bloqueados e terão que esperar
a trava ser liberada novamente
• O acesso ao objeto ainda é possível
se sua referência vazar: se for
acessível fora do bloco
Thread-1

28
Métodos sincronizados
• Um bloco synchronized pode ser usado com a referência this (usa
a trava do objeto onde o código é definido)
• Um bloco synchronized com a referência this que envolva um
método inteiro pode ser transformado em um método synchronized
public void writeData(String text) {
synchronized(this) {
[Link]([Link]);
[Link](text);
[Link]([Link]);
}
} public synchronized void writeData(String text) {
[Link]([Link]);
[Link](text);
[Link]([Link]);
29
}
Repository rep = new Repository();
Thread-2: [Link]("A");
Thread-1: [Link]();
Thread-2 Métodos sincronizados

Thread-1
• A execução de um método,
utiliza a trava do objeto,
impedindo que threads acessem
rep
outros métodos sincronizados do
synchronized writeData()

synchronized readData()
mesmo objeto ao mesmo tempo
• Cada thread obtém acesso
exclusivo ao objeto inteiro
(desde que o objeto seja
acessível apenas em blocos
synchronized)

30
Repository rep = new Repository();
Thread-2: [Link]("A");
Thread-1: [Link]();
Métodos sincronizados

Thread-1
• Quando um dos threads
terminar de executar o método,
rep
a trava é liberada
synchronized writeData()

synchronized readData() • Threads que estiverem


esperando em outros métodos
sincronizados serão
desbloqueados e terão chance
de obter a trava
Thread-2

31
Repository rep = new Repository();
Thread-2: [Link]("A");
Thread-1: [Link]();
Métodos sincronizados

Thread-1
• Apenas um dos threads que
esperam obterá a trava
rep • A obtenção de travas é injusta
synchronized writeData()

synchronized readData() (unfair): a ordem de chegada


não é respeitada

32
Acesso sem travas
Repository rep = new Repository();

Thread-2

Thread-1
Thread-2: [Link]("A");
Thread-1: [Link]();

• Thread-1 e Thread-2 podem ter acesso não- rep

synchronized writeData()
exclusivo ao objeto (ambos podem usar o
objeto ao mesmo tempo) porque um dos

readData()
métodos não requer a trava

• writeData() requer trava e apenas um thread


pode acessá-lo de cada vez

• readData() não requer trava e muitos


threads podem chama-lo ao mesmo tempo
33
volatile
• Um bloco synchronized não serve apenas para garantir acesso
exclusivo. Ele também realiza a comunicação entre threads.
• CPUs armazenam dados em registradores locais, que não são
automaticamente sincronizadas com a memória compartilhada.
• Um algoritmo executando em várias CPUs que acessa uma variável
compartilhada pode não ter a cópia mais recente
• A sincronização com a memória compartilhada é garantida se
– Variável for acessada apenas em blocos ou métodos
synchronized
– Variável for declarada volatile
34
Comunicação entre threads
• Métodos de Object wait(), notify() e notifyAll() liberam a
trava obtida pelo thread ao entrar em um bloco synchronized
• wait() faz o thread esperar uma notificação, liberando a
trava; recebe a trava de volta ao receber uma notificação
• notify() e notifyAll() enviam notificações para que
threads que esperam
• notify() notifica um thread qualquer que recebe a trava
• notifyAll() notifica todos os threads, que disputam a trava

35
Estrutura de wait() e notify()
• wait() deve ser associado a uma condição. Precisa sempre ser
chamado dentro de um loop porque precisa testar a condição
duas vezes
– 1. Ao obter a trava (entrar em synchronized): testa a condição,
e se false, chama wait(), libera a trava e é suspenso
– 2. Ao receber a notificação: testa a condição novamente
synchronized(trava) { // ou método
while(!condição) {
[Link]();
}
// código da tarefa (quando a condição for verdadeira)
notifyAll(); // ou notify(), se threads esperam a mesma condição
} 36
Threads que esperam uma condição
Condition1 Condition1

while( !condition1 ) while( !condition2 )


synchronized( obj )

synchronized( obj )
[Link]() [Link]()

Task Condition2 Task

[Link]() [Link]()

Thread obtém trava e testa a Thread obtém trava e testa a condição, que não é a
condição, que é a esperada, executa a esperada, então entra em estado de espera,
tarefa (mudando a condição) e liberando a trava (depois, ao receber uma notificação,
notifica, liberando a trava testa a condição novamente)
37
Condition2

while( !condition1 )
notify() while( !condition2 )

[Link]()
[Link]()

Got lock
Task Condition2

Condition failed
[Link]() while( !condition1 )

[Link]()
Enviará notificação para
qualquer thread Released lock

Notificação será perdida se o while( !condition2 )


thread que receber a trava
esperar uma condição diferente [Link]()

while( !condition1 )

[Link]() 38
Condition2
notifyAll() while( !condition2 )
while( !condition1 )

[Link]() [Link]()

Task Condition2

Condition failed
[Link]()
while( !condition1 )

[Link]()
Enviará notificação para todos
os threads, que tentarão obter
a trava
Condition passed
while( !condition2 )

[Link]()

while( !condition1 )

[Link]() 39
Condition2
notifyAll()
while( !condition1 ) while( !condition2 )

[Link]() [Link]()

Got lock first


Task Condition2

[Link]()
while( !condition1 )

[Link]()
Enviará notificação para todos
os threads, que tentarão obter
a trava

Se o thread que obtiver a trava while( !condition2 )


espera condition1 ele voltará
ao estado de espera e irá [Link]()
liberar a trava
while( !condition1 )

[Link]() 40
Condition2
notifyAll()
while( !condition1 ) while( !condition2 )

[Link]() [Link]()

Got lock first


Task Condition2

Condition failed
[Link]()
while( !condition1 )
Got lock second
[Link]()
Enviará notificação para todos
os threads, que tentarão obter Released lock
a trava
Condition passed
Se o thread que obtiver a trava while( !condition2 )
espera condition1 ele voltará
ao estado de espera e irá [Link]()
liberar a trava
Lock retained
while( !condition1 )
O primeiro thread que espera
condition2 irá reter a trava [Link]() 41
Exemplo: produtor-consumidor
• Padrão de design clássico
• Um objeto compartilhado tem seu estado alterado por dois tipos
de threads diferentes:
– Produtor: fornece valor (insere dados em fila, grava
informação, etc.)
– Consumidor: usa valor (retira dados de fila, lê e remove
informação, etc.)
• É preciso haver comunicação entre threads: consumidores
saberem quando há dados para consumir; produtores saberem
quando devem produzir

42
public class SharedObject {
private volatile int value = -1;
public boolean isSet() { return value != -1; }
public synchronized boolean set(int v) {
Objeto
try {
while(isSet()) // Condição: valor indefinido
compartilhado
wait();
value = v;
[Link]([Link]().getName() + ": PRODUCED: " + value);
notifyAll(); // avisa a todos os produtores e consumidores
return true;
} catch (InterruptedException e) { return false; }
}

public synchronized boolean reset() {


try {
while (!isSet()) // Condição: valor definido
wait();
[Link]([Link]().getName() + ": CONSUMED: " + value);
value = -1;
notifyAll(); // avisa a todos os produtores e consumidores
return true;
} catch (InterruptedException e) { return false; }
}
43
}
Produtor
public class Producer implements Runnable {
private SharedObject shared;
private static final int TENTATIVAS = 3;

Producer(SharedObject shared) { [Link] = shared; }

@Override public void run() {


for (int i = 0; i < TENTATIVAS; i++) {
if( ![Link](new Random().nextInt(1000)) ) // tenta produzir número
break; // termina o thread se set() retornar false (foi interrompido)
}
[Link]([Link]().getName() + ": Producer DONE.");
}
}

44
Consumidor
public class Consumer implements Runnable {
private SharedObject shared;
private static final int TENTATIVAS = 3;

Consumer(SharedObject shared) { [Link] = shared; }

@Override public void run() {


for (int i = 0; i < TENTATIVAS; i++) {
if(![Link]()) // tenta consumir
break; // termina thread se retornar false (foi interrompido)
}
[Link]([Link]().getName() + ": Consumer DONE.");
}
}

45
2 Produtores + P1: PRODUCED: 616.
C1: CONSUMED: 616.
2 Consumidores P1: PRODUCED: 768.
C2: CONSUMED: 768.
P2: PRODUCED: 773.
SharedObject o = new SharedObject();
C2: CONSUMED: 773.
String[] names = {"C1", "C2", "P1", "P2"};
P1: PRODUCED: 835.
Thread[] threads = { new Thread(new Consumer(o)),
P1: Producer DONE.
new Thread(new Consumer(o)),
C1: CONSUMED: 835.
new Thread(new Producer(o)),
P2: PRODUCED: 933.
new Thread(new Producer(o)) };
C2: CONSUMED: 933.
for(int i = 0; i < [Link]; i++) {
C2: Consumer DONE.
threads[i].setName(names[i]); P2: PRODUCED: 877.
threads[i].start(); P2: Producer DONE.
} Main DONE.
... C1: CONSUMED: 877.
[Link]("Main DONE."); C1: Consumer DONE.
46
IllegalMonitorStateException
• Uma trava precisa ser liberada mas Object trava1 = new Object();
ela não existe (o thread não possui a Object trava2 = new Object();
trava do objeto) synchronized(trava1) {
[Link]();
• Acontece se
}
– wait() ou notify() / notifyAll() IllegalMonitorStateException
forem chamados fora de um bloco
ou método synchronized
– O objeto protegido pelo bloco ou
método synchronized não é o
mesmo objeto usado para chamar
wait(), notify() ou notifyAll()
47
Ciclo de vida de um thread
t1 = new Thread(task)
BLOCKED

Waiting
NEW
for lock

[Link]()

Thread scheduler WAITING


RUNNING RUNNABLE
[Link]()
[Link]()
class Thread {
enum State {NEW, BLOCKED, ...} [Link]()
finished
[Link] getState() {...}
... TIMED_WAITING
}
[Link](timeout)
FINALIZED [Link](timeout)
[Link](timeout)
48
Patterns e anti-patterns
• Não use agendamento baseado em prioridades

• Não use [Link]()

• Não use a classe ThreadGroup

• Nunca use os métodos deprecados stop(), destroy(), suspend(), resume()

• Dê preferência ao framework de execução (ExecutorService) em novas


aplicações que precisem criar e controlar threads explicitamente

• Dê preferência a abstrações de nível mais alto que escondem os detalhes da


programação concorrente: pacote [Link]

• Use frameworks e APIs que responsabilizem-se pelos threads


49
Exercícios
• 4. Execute o exemplo Produtor-Consumidor mostrado. Experimente trocar o
notifyAll() por notify() e verifique o que muda no seu comportamento.
• 5. Escreva uma classe Pilha com métodos sincronizados char pop() e
push(char) que armazenem caracteres em um buffer de seis posições (use um
array de 6 elementos) e um cursor (inteiro) que aponte para o próximo
espaço livre.
• push() insere um caractere no array (havendo espaço) e incrementa a posição
do cursor. Não havendo espaço, espera até um espaço ser liberado (essa é a
condição). Tendo inserido um caractere, notifica threads que queiram consumir.
• pop() devolve o último caractere inserido (havendo caracteres) e decrementa a
posição do cursor. Não havendo caracteres, espera até que um seja inserido.
Tendo consumido um caractere, notifica threads que queiram inserir.
• 6. Implemente um mecanismo Produtor-Consumidor com a pilha do exercício 5:
• a) Escreva um Produtor que insira as 26 letras do alfabeto na Pilha.
• b) Escreva um Consumidor imprima cada caractere encontrado quando chegar.
• c) Teste com uma classe executável que crie uma pilha, consumidor e produtor
50
Utilitários de concorrência
[Link]
• API com classes, interfaces, estruturas de dados para construir aplicações
concorrentes com um nível de abstração mais alto
• Unidades de tempo: classe TimeUnit
• Variáveis atômicas: pacote [Link]
• Travas (locks) e monitores: pacote [Link]
• Coleções concorrentes: diferentes implementações thread-safe de
List, Set, Queue, Deque e Map
• Sincronizadores: semáforos, barreiras, e outros mecanismos de
sincronização
• Executores: serviços de execução, pools de threads, mecanismos de
callback (Callable e Future), temporizadores, paralelismo (ForkJoin)
• Execução não-bloqueante: classe CompletableFuture
51
Unidades de tempo
• A enumeração TimeUnit contém constantes que especificam
a unidade de tempo usada em métodos do pacote
[Link].
• As constantes da enumeração são:
DAYS HOURS MINUTES SECONDS
MILLISECONDS MICROSECONDS NANOSECONDS

• Usadas em métodos que recebem valores de tempo. Ex:


[Link](task, 1, 24, [Link]);

52
[Link]
• Objetos permitem executar operações de duas e três etapas (ex:
a++) atomicamente usando implementações nativas (CAS)
Arrays
Primitives
AtomicIntegerArray
AtomicBoolean
AtomicLongArray
[Link] AtomicReferenceArray

References
AtomicInteger AtomicLong
AtomicReference
DoubleAdder LongAdder
AtomicMarkableReference
DoubleAccumulator LongAccumulator AtomicStampedReference

Updaters

AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater


53
Primitivos com operações thread-safe
• Para get/set volatile é suficiente
• Para operações básicas de 2/3 etapas (ex: a++) use estes objetos
• Para operações com mais etapas, use synchronized ou locks
AtomicBoolean astate = new AtomicBoolean(true);
AtomicInteger acount = new AtomicInteger(100);
AtomicLong aTimestamp = new AtomicLong([Link]().toEpochMilli());

boolean valor = [Link](); // equivalente à leitura volatile


[Link](false); // equivalente à gravação volatile

[Link](100,-34); // se 100, mude para -34


int resultado = [Link](234); // resultado = 200

long agora = [Link](); // timestamp++


long mais2ms = [Link](); // ++timestamp;
long tsAmanha = [Link](24*2300*1000); 54
Travas de exclusão mútua
StampedLock
readLock(): long
writeLock(): long
unlock(stamp:long)
tryOptimisticRead()

asReadLock(): Lock
asWriteLock(): Lock

55
Interface Lock
• É semanticamente equivalente a uma trava intrínseca com synchronized:
Object mutex = new Object();
synchronized(mutex) { // bloqueia se outro thread chegar primeiro
// acessar recurso protegido pela trava
}

• Usando Lock:
Lock mutex = new ReentrantLock();
[Link](); // bloqueia se outro thread chegar primeiro
try {
// acessar recurso protegido pela trava
} finally {
[Link]();
}
56
Condition
• O método newCondition() cria um objeto que representa uma condição nova
(trava pode estar associada a várias condições)

Condition cheio = [Link]();

• Um objeto Condition é uma abstração que representa uma condição, mas não
encapsula nenhum predicado ou teste.

interface Condition {
void await()
Em situações onde notifyAll é
boolean await(long time, TimeUnit unit)
necessário (thread espera
long awaitNanos(long nanosTimeout) condições diferentes) é mais
void awaitUninterruptibly() eficiente usar Lock + Condition
void signal()
void signalAll()
} 57
public class SharedObject {
private final Lock lock = new ReentrantLock();
private final Condition canWrite = [Link](); Objeto compartilhado
private final Condition mayRead = [Link]();
private volatile int value = -1; Compare com exemplo Produtor-
Consumidor mostrado
public boolean isSet() { value != -1; }
anteriormente com wait/notifyAll
public boolean set(int v) {
[Link]();
try {
while (isSet()) [Link](); // wait for writing permission
value = v;
[Link]([Link]().getName() + ": PRODUCED: " + value + ".");
[Link](); // Signal to readers
} catch (InterruptedException e) { return false; } finally { [Link](); }
return true;
}
public boolean reset() {
[Link]();
try {
while (!isSet()) [Link](); // wait for permission to read
[Link]([Link]().getName() + ": CONSUMED: " + value + ".");
value = -1;
[Link](); // Signal to writers
} catch (InterruptedException e) { return false; } finally { [Link](); }
return true;
}
} 58
}
Coleções e threads
• Implementações das coleções do pacote [Link] podem ser ou não thread-safe

• Usar uma coleção thread-safe desnecessariamente (quando há apenas um


thread) poderá ter um impacto negativo na performance

• Por outro lado, é preciso garantir que a coleção continue a funcionar quando
acessada por múltiplos threads

• Há várias implementações de algoritmos concorrentes em [Link]

• Alguns sincronizam a coleção inteira, outros usam estratégias intermediárias


como: confinamento de threads em parte da estrutura, coleções imutáveis que
fazem uma nova cópia a cada gravação, etc.

59
Coleções imutáveis (thread-safe)
• Se uma aplicação preenche uma coleção uma única vez, e depois apenas acessa
apenas para leitura, pode ser construída como um objeto não-modificável
• Métodos de Collections criam coleções não-modificáveis:
unmodifiableCollection(), unmodifiableSet(), unmodifiableList(),
unmodifiableMap(), etc.
• Podem ser usadas em ambientes concorrentes sem locks
List<String> listaMutavel = new ArrayList<>();
[Link]("Um");
[Link]("Dois");
List<String> listaImutavel = [Link](listaMutavel);
listaMutavel = null; // apenas acesso imutável é permitido agora
for(String s : listaImutavel)
[Link](s);
List<Integer> vazia = [Link](); // Lista imutável e vazia
Set<Integer> unidade = [Link](12); // Set imutável
60
Coleções sincronizadas
• Collections possui métodos de fábrica que constroem coleções com
todos os métodos sincronizados, que delega para os elementos de
uma coleção comum
• O acesso deve ser feito exclusivamente através da referência criada
• Há uma versão sincronizada para cada interface e sub-interface pacote
[Link] (exceto Queue)
List<String> lista = new ArrayList<>();
List<String> listaThreadSafe = [Link](lista);
lista = null; // apenas acesso sincronizado é permitido agora

Map<String, Double> mapa = new HashMap<>();


Map<String, Double> mapaThreadSafe = [Link](mapa);
mapa = null; 61
ConcurrentHashMap
• Há três implementações thread-safe de HashMap em Java:
– Hashtable - Sincroniza a tabela inteira (apenas um thread pode ter
acesso ao Map ao mesmo tempo)
– [Link]( HashMap ) - Recebe um HashMap
(que não é thread-safe) e intercepta as chamadas através de
métodos synchronized (alcança o mesmo objetivo que Hashtable).
– ConcurrentHashMap - Emprega um algoritmo de concorrência que
reduz o uso do travamento, usando locks separadas para leitura e
gravação, relaxando o contrato de size(), iterator(), etc. e travando
apenas um bucket de cada vez, permitindo múltiplas leituras e
gravações simultâneas.

62
SynchronizedMap / Hashtable<Point, String>
One lock for entire table

0, 8, 16 Point(0,0) Point(1,1) Point(2,2)

1, 9 Point(0,1) Point(1,2)

Thread 1 (owns lock)


get(new Point(0,2)) 2,10 Point(0,2) Point(1,3)

3 Point(0,3)

Thread 2 (waiting for lock)


get(new Point(0,4)) 4, 28 Point(0,4) Point(4,0)

21 Point(3,0)

Thread 3 (waiting for lock)


put(new Point(3,1), "(3,1)") 14, 22 Point(2,0) Point(3,1)

7, 15 Point(1,0) Point(2,1)

63
Hashcode Buckets Keys
ConcurrentHashMap<Point, String>
One write lock for each bucket

Thread 1 (non-blocking read) 0, 8, 16 Point(0,0) Point(1,1) Point(2,2)


get(new Point(1,3))

Thread 2 (non-blocking read) 1, 9 Point(0,1) Point(1,2)


get(new Point(0,2))

2,10 Point(0,2) Point(1,3)

Thread 3 (owns write lock) 3 Point(0,3)


put(new Point(0,4), "(x,x)")

Thread 4 (non-blocking read)*


4, 28 Point(0,4) Point(4,0)
get(new Point(0,4))

21 Point(3,0) locked for writing

Thread 5 (owns write lock)


14, 22 Point(2,0) Point(3,1)
put(new Point(2,0), "(y,y)")
Thread 6 (waiting for write lock)
put(new Point(3,1), "(z,z)") 7, 15 Point(1,0) Point(2,1)

64
* may read obsolete values Hashcode Buckets Keys
Executor
• Executor representa uma estratégia de execução
• É uma interface funcional. Método: void execute(Runnable)
• Desacopla o envio de uma tarefa para processamento dos detalhes
de como a tarefa será executada (ex: se criará threads ou não)

Sem Executor Com Executor


void iniciarThreads() { void iniciarThreads(Executor executor) {
new Thread(new RunnableUm()).start(); [Link](new RunnableUm());
new Thread(new RunnableUm()).start(); [Link](new RunnableDois());
} }

Tarefas executam em threads distintos Executor encapsula detalhes (não é possível saber se
tarefas executam em threads separados)
65
Implementações de Executor
os detalhes da execução dos threads é
class UmThreadPorTarefa implements Executor {
encapsulada nos métodos execute()
@Override
public void execute(Runnable tarefa) {
class MultipleThreadExecutor implements Executor {
new Thread(tarefa).start();
private Thread[] threads;
}
public MultipleThreadExecutor(int threadCount) {
}
threads = new Thread[threadCount];
iniciarThreads(new UmThreadPorTarefa());
}
@Override public void execute(Runnable task) {
[Link]([Link]().getSimpleName() + ", Threads:");
for (Thread t : threads) {
t = new Thread(task);
class ExecutorSincrono implements Executor {
[Link]();
@Override
}
public void execute(Runnable tarefa) {
}
[Link]();
}
}
iniciarThreads(new MultipleThreadExecutor(5));
}
66
iniciarThreads(new ExecutorSincrono());
ExecutorService
• Estende Executor com métodos adicionais para a submissão de
coleções de tarefas, operações assíncronas e métodos para finalização
do serviço. Ex: shutdown()

• Para criar um ExecutorService é preciso instanciar e configurar um


ThreadPoolExecutor, ScheduledThreadPoolExecutor ou ForkJoinPool

• Métodos da classe utilitária Executors devolvem implementações pré-


configuradas:
– [Link]()
– [Link](tamanho)
– [Link](tamanho)
– [Link]()
– [Link]()
67
[Link]
• Usando [Link](limite)
– Threads que ultrapassarem limite máximo esperam liberação
[Link]("> 4 threads running in parallel");
ExecutorService e = [Link]();
[Link]( new ConcurrentTask("Ch-One") );
[Link]( new ConcurrentTask("Ch-Two") );
[Link]( new ConcurrentTask("Ch-Three") );
[Link]( new ConcurrentTask("Ch-Four") );
[Link](); // finaliza quando todos terminarem
[Link]("> Executor will shutdown after tasks.");

main Thread-1 Thread-2 Thread-3 Thread-4

Ch-Two
Ch-One Ch-Four
Ch-Three
time

68
[Link]
• Usando [Link](limite)
– Threads que ultrapassarem limite máximo esperam liberação
[Link]("4 trying to run in pool of 3");
ExecutorService e = [Link](3);
[Link]( new ConcurrentTask("Fx-One") );
[Link]( new ConcurrentTask("Fx-Two") );
[Link]( new ConcurrentTask("Fx-Three") );
[Link]( new ConcurrentTask("Fx-Four") );
[Link](); // finaliza quando todos terminarem
[Link]("> Executor will shutdown after tasks.");

main
Thread-1 Thread-2 Thread-3
Fx-One Fx-Two Fx-Three
time

Fx-Four
69
Callbacks
• Outro mecanismo suportado pelo ExecutorService é o callback: objeto Future
notificado quando a tarefa terminar e que guarda o seu resultado (se houver).

• Um Future é retornados pelos métodos submit(tarefa) de ExecutorService

Future<?> callback = [Link](tarefa);

• O Future retornado pode bloquear o thread até o fim da tarefa, através de get()
[Link](); // o thread bloqueia aqui enquanto a tarefa não terminar
[Link]("Tarefa concluída.");

• get() lança InterruptedException e ExecutionException; também pode ser


chamado com timeout e a tarefa pode ser interrompida com cancel()
try {
[Link](10, [Link]);
} catch (InterruptedException | ExecutionException | TimeoutException e1) {
[Link](true); // cancela após esperar 10 segundos pelo resultado
70
}
Future
Future<V> future = [Link](task)

Thread-1
Thread-2 [Link](task)

Return Future Future<V>


Begin task
RESULT
ExecutorService

... waiting get()


Processing
time

task
...
...

Future<V>
End task
Set result RESULT

V get() result

71
Callable<V>
• Interface funcional que representa uma tarefa assíncrona

• Similar a Runnable, mas seu método call(), diferentemente de run() pode


retornar um valor e lançar uma exceção

interface Callable<V> {
V call() throws Exception;
}

• Métodos submit() que recebem Callable<V> retornam Future<V> que


armazena valor retornado por V call() quando a tarefa terminar.

ExecutorService service = [Link]();


Callable<Double> callableTask = () -> [Link](144); // expr. lambda!
Future<Double> resultado = [Link](callableTask);
[Link]("Resultado: " + [Link]());
72
Usando Future e Callable
• Exemplo de tarefa, que abre um arquivo de texto,
submetida a um serviço de execução
• O resultado estará em [Link]()
ExecutorService service = [Link]();
Callable<String> callableTask =
new FileOpener([Link]("[Link]") + "/target/classes/[Link]");
Future<String> fileContents = [Link](callableTask);
try {
[Link]("\nResult: \n" + [Link](10, [Link]));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
[Link]("Execution exception, interruption or timeout. Aborting.");
} 73
Exercícios
• 7. Altere o exercício 3 para que cada operação dos dois threads grave
o resultado num mesmo um ArrayList compartilhado (use
[Link]). Depois que ambos terminarem, o
thread main deve somar todos os valores e exibir o resultado.
• 8. Reimplemente o exemplo/exercício 4 trocando os blocos
synchronized com wait() e notify() por uma implementação usando
ReentrantLock e Condition
• 9. Reimplemente o exercício 5/6 com ReentrantLock e Condition
• 10. Reimplemente o exercício 1 usando um Executor (use um pool
ExecutorService a partir do utilitário Executors)
• 11. Altere o exercício 7 para que ele use um ExecutorService, Future
e Callable (substituindo Runnable, Thread e join())

74
JAVA 8
para programadores
02/02/2015

Helder da Rocha
helder@[Link]

Você também pode gostar