Você está na página 1de 47

Programação Concorrente

Threads em Java

Prof. Antonio Felipe Podgorski Bezerra, M.Sc.


afbezerra@unicarioca.edu.br
Sumário

´ Executar tarefas simultaneamente utilizando


Threads.
´ Entender o escalonador de Threads.
´ Problemas com concorrência e sincronismos
em Threads.
´ Exercícios práticos
Threads

´ A maioria dos programas são escritos de modo


sequencial com um ponto de início (método
main( )), uma sequência de execuções e um
ponto de término.
´ Em qualquer dado instante existe apenas uma
instrução sendo executada.
´ O que são Threads?
´ É um simples fluxo sequencial de execução que
percorre um programa.
´ Multithreading: o programador especifica que os
aplicativos contêm fluxos de execução (threads),
cada thread designando uma parte de um
programa que pode ser executado
simultaneamente com outras threads.
Threads

´ Um thread não é somente um programa,


mas executa dentro de um programa. É um
fluxo único de controle sequencial dentro de
um programa.
Threads

Thread Processo

Mais Leve Mais Pesado

Recursos compartilhados Recursos Próprios(I/O, ...)

Endereçamento compartilhado Endereçamento Próprio

Ambiente de execução Ambiente de Execução próprio


Compartilhada

Existe dentro de um Processo Possui ao menos um thread


Benefícios de Thread
— A criação e terminação de uma thread nova é em
geral mais rápida do que a criação e terminação
de um processo novo.

— A comutação de contexto entre duas threads é


mais rápido do que entre dois processos.

— A comunicação entre threads é mais rápida do que


a comunicação entre processos.

— Multiprogramação usando o modelo de threads é


mais simples e mais portável do que
multiprogramação usando múltiplos processos.
Implementação de uma
Thread
Existem duas formas de criar explicitamente
um thread em Java:

— Estendendo a classe Thread e instanciando


um objeto desta nova classe.
— Implementando a interface Runnable e
passando um objeto desta nova classe
como argumento do construtor da classe
Thread.

Nos dois casos a tarefa a ser executado pelo


thread deverá ser escrita no método run( ).
Implementação de uma
Thread
— Em Java, usamos a classe Thread do
pacote java.lang para criarmos linhas de
execução paralelas.

— A classe Thread recebe como argumento


um objeto com o código que desejamos
rodar.

— Por exemplo, no programa de PDF e barra


de progresso:
Implementação de uma
Thread
public class GeraPDF {
public void rodar( ) {
// lógica para gerar o pdf...
}
}
public class BarraDeProgresso {
public void rodar( ) {
// mostra barra de progresso e atualizando...
}
}
Implementação de uma
Thread
— E, no método main, criamos os objetos e
passamos para a classe Thread. O método
start é responsável por iniciar a execução
da Thread:
Porém, o código não compilará.
public class MeuPrograma {

Como a classe Thread sabe que deve chamar


public static void main (String[] args) {
GeraPDF gerapdf = new GeraPDF();
o método public void rodar ( )?
Thread threadDoPdf = new Thread(gerapdf);
Como ela sabe que nome de método
threadDoPdf.start();
daremos e que ela deve chamar esse
método especial?
BarraDeProgresso barraDeProgresso = new BarraDeProgresso();
Thread threadDaBarra = new Thread(barraDeProgresso);
Falta na verdade um contrato entre as nossas
threadDaBarra.start();
classes
} a serem executadas e a classe Thread.
Implementação de uma
Thread
— Esse contrato existe e é feito pela interface
Runnable: devemos dizer que nossa classe
é “executável” e que segue esse contrato.

— Na interface Runnable, há apenas um


método chamado run( ).

— Basta implementá-lo, “assinar” o contrato e


a classe Thread já saberá executar nossa
classe:
Implementação de uma
Thread
public class GeraPDF implements Runnable {
@Override
public void run( ) {
// lógica para gerar o pdf...
}
}
public class BarraDeProgresso implements Runnable {
@Override
public void run( ) {
// mostra barra de progresso e atualizando...
}
}
Implementação de uma
Thread
— A classe Thread recebe no construtor um
objeto que é um Runnable, e seu método
start chama o método run da nossa classe.

— Repare que a classe Thread não sabe qual


é o tipo específico da nossa classe; para
ela, basta saber que a classe segue o
contrato estabelecido e possui o método
run.
— É o bom uso de interfaces, contratos e
polimorfismo na prática!
Implementação de uma
Thread
— A classe Thread implementa Runnable.
Então, você pode criar uma subclasse dela
e reescrever o run que, na classe Thread,
não faz nadasse.

public class GeraPDF extends Thread {


public void run( ) {
// to do:
}
}
Implementação de uma
Thread
— E, como nossa classe é uma Thread,
podemos usar o start diretamente:

GeraPDF gera = new GeraPDF();


gera.start( );
— Apesar de ser um código mais simples,
você está usando herança apenas por
“preguiça” (herdamos um monte de
métodos mas usamos apenas o run), e
não por polimorfismo, que seria a
grande vantagem. Prefira implementar
Runnable a herdar de Thread.
Solução Baseada em
Herança da Classe Thread

Thread

GeraPDF instanciação
Implementação de uma
Thread
public class Programa implements Runnable {
private int id;
public void setId(int id) {
this.id = id;
}
@Override
public void run( ) {
for (int i = 0; i < 10000; i++) {
System.out.println("Programa " + id + " valor: " +
i);
}
}
}
Implementação de uma
Thread
public class Threads {
public static void main(String[] args) {
Thread t1 = new Thread(new Programa());
t1.start();

Thread t2 = new Thread(new Programa());


t2.start();
}
}
Solução Baseada na
Interface Runnable

Thread
Classe A

referência

Programa
Runnable
Exercícios práticos
1) Crie a classe GeradorRelatorio, que
contém o código a ser executado por uma
thread:
Exercícios práticos
2) Implemente o código para Criação e
execução de uma Thread, usando um objeto
GeradorRelatorio:
Exercícios práticos
3) Altere o código de execução de thread,
desenvolvido no exercício anterior, incluindo
a mensagem final:
Resultado da execução
— Por que a mensagem final aparece antes
da execução da Thread?
— Thread == novo processo independente
Exercícios práticos
4) Crie a classe BarraDeProgresso, que será
executada por uma Thread, durante a
impressão do relatório:
Exercícios práticos
5) Agora, nossa aplicação deve imprimir o
relatório e exibir a barra, ao mesmo tempo:
Resultado da execução
— Os processos concorrem por tempo de
execução no processador;
— As regras de escalonamento definem o
tempo de execução de cada processo.
Escalonador e trocas de
contexto
— É serviço do escalonador de
threads(scheduler) alternar o tempo de
execução de cada Thread iniciada(start).
— A troca de contexto ocorre quando o
escalonador salva o estado da thread
atual, para recuperar depois, e pára sua
execução.
— Neste momento, é iniciada uma nova
thread.
— ou é recuperado o estado de outra que
estava parada, voltando a executar;
Escalonador e trocas de
contexto
— Se rodarmos o exemplo dos slides 17 e 18, qual será a
saída?
— De um a mil e depois de um a mil?
— Provavelmente não, senão seria sequencial.
— Ele imprimirá 1 de t1, 1de t2, 2 de t1, 2 de t2, 3 de t1 e
3 de t2 e etc... Exatamente intercalado?
— Na verdade, não sabemos exatamente qual é a
saída.
— Se executar o programa várias vezes, observará que
em cada execução a saída é um pouco diferente.
Ciclo de vida
Prioridades em Threads
— Cada thread possui uma prioridade de execução que
vai de Thread.MIN_PRIORITY (igual a 1) a
Thread.MAX_PRIORITY (igual a 10).
— Importante: uma thread herda a prioridade da
thread que a criou.

— O algoritmo de escalonamento sempre deixa a thread


(runnable) de maior prioridade executar.

— A thread de maior prioridade preempta as outras


threads de menor prioridade.
Escalonamento

Fonte: Java, Como programar.


Deitel, 6ª Edição
Prioridades em Threads
— Se todas as threads tiverem a mesma
prioridade, a CPU é alocada para todos,
um de cada vez, em modo round-robin.

— getPriority(): obtém a prioridade corrente


da thread;

— setPriority(): define uma nova prioridade.


Prioridades em Threads
class BaixaPrioridade extends Thread {
public void run() {
setPriority(Thread.MIN_PRIORITY);
for(;;) {
System.out.println("Thread de baixa prioridade
executando -> 1");
}
}
}
class AltaPrioridade extends Thread {
public void run() {
setPriority(Thread.MAX_PRIORITY);
for(;;) {
for(int i=0; i<5; i++)
System.out.println("Thread de alta prioridade
executando -> 10");
try {
sleep(100);
} catch(InterruptedException e) {
System.exit(0);
}
}
}
}
Prioridades em Threads
class Lançador {
public static void main(String args[ ]) {
AltaPrioridade a = new AltaPrioridade();
BaixaPrioridade b = new BaixaPrioridade();
System.out.println("Iniciando threads...");
b.start();
a.start();
// Deixa as outras threads iniciar a execução.
// O método yield(), cede o processamento para outra thread.
Thread.currentThread().yield();
System out println("Main feito");
}
}

Iniciando threads...
Main feito
Thread de alta prioridade executando -> 10
Thread de alta prioridade executando -> 10
Thread de baixa prioridade executando -> 1
Thread de baixa prioridade executando -> 1
Thread de baixa prioridade executando -> 1
Thread de alta prioridade executando -> 10
Thread de alta prioridade executando -> 10
Thread de baixa prioridade executando -> 1
Problemas com
Concorrência Sincronização
de Threads
— Quando muitas threads são executadas
muitas vezes é necessário sincronizar suas
atividades.

— Por exemplo, prevenir o acesso


concorrente a estruturas de dados
(variáveis, vetores, matrizes etc.) no
programa que são compartilhados entre as
threads;
Interferência entre Threads

— Erros introduzidos quando múltiplas threads


acessam um dado compartilhado.

— Essa interferência pode fazer com que o


resultado não seja esperado.
Interleaving
— Interferência acontece quando duas
operações, rodando em threads diferentes,
atuam sobre o mesmo objeto.

— Interleaving significa que os passos


realizados pelas operações se sobrepõem
— De forma não-determinística.
Exercícios práticos
— Imagine que temos a necessidade de
imprimir os nomes das frutas de um cesto;

— Queremos “dar um tempo” para a leitura


dos usuário;

— Com isso, precisamos que, após a


impressão de um nome, seja realizada uma
pausa na execução;

— Para isso, usamos Thread.sleep(tempo).


Exercícios práticos
6) Cria a cesta de frutas:
public class CestaFrutas implements Runnable {
@Override
public void run() {
//Criação da lista de frutas
String [ ] ingredientes = {"Banana", "Mamão", "Maçã", "Abacate"};
System.out.println("Início do Run()");
//Impressão da lista de frutas
for (String fruta : ingredientes) {
System.out.println(fruta);
//Dormindo por 3 segundos
try {
Thread.sleep(3*1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("Fim do Run()");
}
}
Exercícios práticos
7) Implemente e execute o código a seguir:
public class ThreadCestaFrutas {
public static void main(String[] args) {
//Criação do objeto executável
CestaFrutas salada = new CestaFrutas();
//Criação da Thread
Thread executar = new Thread(salada);
//Execução da Thread
executar.start();
}
}

8) Qual o resultado?
Exercícios práticos
— Consultando a documentação java,
verificamos que a classe Thread
implementa Runnable.

— Isso nos permite criar uma classe filha de


Thread e sobrescrever o método run().

— Isso nos permite colocar na mesma classe o


executor e o executado.
Exercícios práticos
8) Implemente a herança de Thread.
Garbage Collector
— O Garbage Collector (coletor de lixo) é
uma Thread responsável por jogar fora
todos os objetos que não estão sendo
referenciados;
— Imaginemos o código a seguir:
Garbage Collector
— Com a execução do item 1, são criados
dois objetos ContaBancaria e atribuídos às
referências conta1 e conta2.

— No item 2, as duas referências passam a


apontar para o mesmo objeto.

— Neste momento, quantos objetos existem


na memória? Um ou dois?

— Perdemos a referência a um dos objetos.


Garbage Collector
— O objeto sem referência não pode mais ser
acessado.

— Podemos afirmar que ele saiu da memória?

— Como Garbage Collector é uma Thread, por


ser executado a qualquer momento.

— Com isso, dizemos que o objeto sem


referência está disponível para coleta.
Garbage Collector
— Você consegue executar o Garbage
Collector.

— Mas chamando o método estático


System.gc() você está sugerindo que a JVM
rode o Garbage Collector naquele
momento.

— Sua sugestão pode ser aceita ou não.

— Você não deve basear sua aplicação na


execução do Garbage Collector.
DÚVIDAS?