0% acharam este documento útil (0 voto)
5 visualizações

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
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
5 visualizações

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
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 75

11

Threads

Helder da Rocha
[email protected]
Conteúdo
1.Introdução 8.Objetos
2.Sintaxe 9.Coleções
3.Expressões 10.Datas
4.Arrays e Strings 11.Threads
5.Classes 12.Programação funcional
6.Interfaces 13.Arquivos e I/O
7.Exceções 14.Bancos 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 java.util.concurrent)

3
Threads em Java
• Representados pela classe java.lang.Thread.
• 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 = Thread.currentThread();

System.out.println("Nome do thread: "


+ principal.getName()); // imprime main

System.out.println("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 java.lang.Runnable

5
Implementando Runnable
• A interface java.lang.Runnable é 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() {
System.out.println("Hello world paralelo!");
System.out.println("Eu sou o thread: "
+ Thread.currentThread().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();
paralelo.run();
System.out.println("Thread principal: " +
Thread.currentThread().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():
t.start();

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);
t1.start();
System.out.println("Thread principal: " +
Thread.currentThread().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
t1.start();
System.out.print("Fim"); run() {
} for(int i = 0; i < 10000; i++)
System.out.println(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() {
System.out.println("Hello world paralelo!");
}
});
t1.start();
System.out.println("Thread principal: "
+ Thread.currentThread().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(
() -> System.out.println("Hello world paralelo!") );
t1.start();
System.out.println("Thread principal: "
+ Thread.currentThread().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 Thread.currentThread()):
– 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 = Thread.interrupted();
System.out.println(">INTERRUPT flag: " + interrupt);
}
System.out.println("INTERRUPTED flag: " + interrupt);
System.out.println("Thread "
+ Thread.currentThread().getName() + " is DONE!");
}
}

15
Pondo threads para dormir
• O thread que está executando pode suspender sua execução
por um determinado tempo chamando
Thread.sleep(milissegundos)
• Para dormir 1 segundo (1000 milissegundos):
Thread.sleep(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 t1.start();
// main thread executa suas tarefas
t1.interrupt(); // sets interrupt in t1
class RandomLetters implements Runnable { System.out.println("main is DONE!");
@Override public void run() { }
try { }
while(true) {
System.out.print(" " + (char)('A' + new Random().nextInt(26)));
Thread.sleep(200);
}
} catch (InterruptedException e) {
System.out.println("\n" + Thread.currentThread().getName() + " interrupted.");
System.out.println("INTERRUPTED flag: " + Thread.currentThread().isInterrupted());
}
System.out.println("Thread " + Thread.currentThread().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
Thread.sleep(100); InterruptedException
} catch(InterruptedException e) { desliga flag de interrupção
System.out.println("Thread interrompido que não será finalizado."));
Thread.currentThread().interrupt(); // IMPORTANTE!
}
} Liga novamente o flag
System.out.println("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 t2.join(), 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++) System.out.println(i);
});
t1.start();
System.out.println("Waiting for " + t1.getName());
t1.join();
System.out.println("Thread main is DONE!");
20
Esperando threads terminarem
JVM

Thread: main

main() {
Runnable task = new RunnableLoop();
Thread t1 = new Thread(task);
Thread: Thread-1
t1.start();
t1.join();
run() {
for(int i = 0; i < 10000; i++)
WAITING
System.out.println(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 (Math.sqrt) 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 (Math.cbrt) 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) {
buffer.append(this.prefix);
buffer.append(text);
buffer.append(this.suffix);
}
} public synchronized void writeData(String text) {
buffer.append(this.prefix);
buffer.append(text);
buffer.append(this.suffix);
29
}
Repository rep = new Repository();
Thread-2: rep.writeData("A");
Thread-1: rep.readData();
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: rep.writeData("A");
Thread-1: rep.readData();
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: rep.writeData("A");
Thread-1: rep.readData();
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: rep.writeData("A");
Thread-1: rep.readData();

• 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) {
trava.wait();
}
// 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 )
obj.wait() obj.wait()

Task Condition2 Task

obj.notify() obj.notify()

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 )

obj.wait()
obj.wait()

Got lock
Task Condition2

Condition failed
obj.notify() while( !condition1 )

obj.wait()
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 obj.wait()

while( !condition1 )

obj.wait() 38
Condition2
notifyAll() while( !condition2 )
while( !condition1 )

obj.wait() obj.wait()

Task Condition2

Condition failed
obj.notifyAll()
while( !condition1 )

obj.wait()
Enviará notificação para todos
os threads, que tentarão obter
a trava
Condition passed
while( !condition2 )

obj.wait()

while( !condition1 )

obj.wait() 39
Condition2
notifyAll()
while( !condition1 ) while( !condition2 )

obj.wait() obj.wait()

Got lock first


Task Condition2

obj.notifyAll()
while( !condition1 )

obj.wait()
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á obj.wait()
liberar a trava
while( !condition1 )

obj.wait() 40
Condition2
notifyAll()
while( !condition1 ) while( !condition2 )

obj.wait() obj.wait()

Got lock first


Task Condition2

Condition failed
obj.notifyAll()
while( !condition1 )
Got lock second
obj.wait()
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á obj.wait()
liberar a trava
Lock retained
while( !condition1 )
O primeiro thread que espera
condition2 irá reter a trava obj.wait() 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;
System.out.println(Thread.currentThread().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();
System.out.println(Thread.currentThread().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) { this.shared = shared; }

@Override public void run() {


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

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

Consumer(SharedObject shared) { this.shared = shared; }

@Override public void run() {


for (int i = 0; i < TENTATIVAS; i++) {
if(!shared.reset()) // tenta consumir
break; // termina thread se retornar false (foi interrompido)
}
System.out.println(Thread.currentThread().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 < threads.length; i++) {
C2: Consumer DONE.
threads[i].setName(names[i]); P2: PRODUCED: 877.
threads[i].start(); P2: Producer DONE.
} Main DONE.
... C1: CONSUMED: 877.
System.out.println("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) {
trava2.wait();
• 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

t1.start()

Thread scheduler WAITING


RUNNING RUNNABLE
obj.wait()
t1.join()
class Thread {
enum State {NEW, BLOCKED, ...} task.run()
finished
Thread.State getState() {...}
... TIMED_WAITING
}
obj.wait(timeout)
FINALIZED t1.join(timeout)
Thread.sleep(timeout)
48
Patterns e anti-patterns
• Não use agendamento baseado em prioridades

• Não use Thread.yield()

• 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 java.util.concurrent

• 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
java.util.concurrent
• 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 java.util.concurrent.atomic
• Travas (locks) e monitores: pacote java.util.concurrent.locks
• 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
java.util.concurrent.
• As constantes da enumeração são:
DAYS HOURS MINUTES SECONDS
MILLISECONDS MICROSECONDS NANOSECONDS

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


service.scheduleAtFixedRate(task, 1, 24, TimeUnit.HOURS);

52
java.util.concurrent.atomic
• Objetos permitem executar operações de duas e três etapas (ex:
a++) atomicamente usando implementações nativas (CAS)
Arrays
Primitives
AtomicIntegerArray
AtomicBoolean
AtomicLongArray
java.lang.Number 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(Instant.now().toEpochMilli());

boolean valor = astate.get(); // equivalente à leitura volatile


astate.set(false); // equivalente à gravação volatile

acount.compareAndSet(100,-34); // se 100, mude para -34


int resultado = acount.addAndGet(234); // resultado = 200

long agora = aTimestamp.getAndIncrement(); // timestamp++


long mais2ms = aTimestamp.incrementAndGet(); // ++timestamp;
long tsAmanha = aTimestamp.addAndGet(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();
mutex.lock(); // bloqueia se outro thread chegar primeiro
try {
// acessar recurso protegido pela trava
} finally {
mutex.unlock();
}
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 = lock.newCondition();

• 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 = lock.newCondition(); Objeto compartilhado
private final Condition mayRead = lock.newCondition();
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) {
lock.lock();
try {
while (isSet()) canWrite.await(); // wait for writing permission
value = v;
System.out.println(Thread.currentThread().getName() + ": PRODUCED: " + value + ".");
mayRead.signal(); // Signal to readers
} catch (InterruptedException e) { return false; } finally { lock.unlock(); }
return true;
}
public boolean reset() {
lock.lock();
try {
while (!isSet()) mayRead.await(); // wait for permission to read
System.out.println(Thread.currentThread().getName() + ": CONSUMED: " + value + ".");
value = -1;
canWrite.signal(); // Signal to writers
} catch (InterruptedException e) { return false; } finally { lock.unlock(); }
return true;
}
} 58
}
Coleções e threads
• Implementações das coleções do pacote java.util 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 java.util.concurrent

• 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<>();
listaMutavel.add("Um");
listaMutavel.add("Dois");
List<String> listaImutavel = Collections.unmodifiableList(listaMutavel);
listaMutavel = null; // apenas acesso imutável é permitido agora
for(String s : listaImutavel)
System.out.println(s);
List<Integer> vazia = Collections.emptyList(); // Lista imutável e vazia
Set<Integer> unidade = Collections.singleton(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
java.util (exceto Queue)
List<String> lista = new ArrayList<>();
List<String> listaThreadSafe = Collections.synchronizedList(lista);
lista = null; // apenas acesso sincronizado é permitido agora

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


Map<String, Double> mapaThreadSafe = Collections.synchronizedMap(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)
– Collections.synchronizedMap( 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(); executor.execute(new RunnableUm());
new Thread(new RunnableUm()).start(); executor.execute(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) {
System.out.println(this.getClass().getSimpleName() + ", Threads:");
for (Thread t : threads) {
t = new Thread(task);
class ExecutorSincrono implements Executor {
t.start();
@Override
}
public void execute(Runnable tarefa) {
}
tarefa.run();
}
}
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:
– Executors.newCachedThreadPool()
– Executors.newFixedThreadPool(tamanho)
– Executors.newScheduledThreadPool(tamanho)
– Executors.newSingleThreadExecutor()
– Executors.newWorkStealingPool()
67
Executors.newCachedThreadPool
• Usando Executors.newFixedThreadPool(limite)
– Threads que ultrapassarem limite máximo esperam liberação
System.out.println("> 4 threads running in parallel");
ExecutorService e = Executors.newCachedThreadPool();
e.execute( new ConcurrentTask("Ch-One") );
e.execute( new ConcurrentTask("Ch-Two") );
e.execute( new ConcurrentTask("Ch-Three") );
e.execute( new ConcurrentTask("Ch-Four") );
e.shutdown(); // finaliza quando todos terminarem
System.out.println("> Executor will shutdown after tasks.");

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

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

68
Executors.newFixedThreadPool
• Usando Executors.newFixedThreadPool(limite)
– Threads que ultrapassarem limite máximo esperam liberação
System.out.println("4 trying to run in pool of 3");
ExecutorService e = Executors.newFixedThreadPool(3);
e.execute( new ConcurrentTask("Fx-One") );
e.execute( new ConcurrentTask("Fx-Two") );
e.execute( new ConcurrentTask("Fx-Three") );
e.execute( new ConcurrentTask("Fx-Four") );
e.shutdown(); // finaliza quando todos terminarem
System.out.println("> 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 = executor.submit(tarefa);

• O Future retornado pode bloquear o thread até o fim da tarefa, através de get()
callback.get(); // o thread bloqueia aqui enquanto a tarefa não terminar
System.out.println("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 {
callback.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e1) {
callback.cancel(true); // cancela após esperar 10 segundos pelo resultado
70
}
Future
Future<V> future = service.submit(task)

Thread-1
Thread-2 service.submit(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 = Executors.newCachedThreadPool();


Callable<Double> callableTask = () -> Math.sqrt(144); // expr. lambda!
Future<Double> resultado = service.submit(callableTask);
System.out.println("Resultado: " + resultado.get());
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 fileContents.get()
ExecutorService service = Executors.newCachedThreadPool();
Callable<String> callableTask =
new FileOpener(System.getProperty("user.dir") + "/target/classes/sample.txt");
Future<String> fileContents = service.submit(callableTask);
try {
System.out.println("\nResult: \n" + fileContents.get(10, TimeUnit.SECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
System.out.println("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
Collections.synchronizedList). 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
[email protected]

Você também pode gostar