Programação Orientada a Objetos (DPADF 0063)bruno/disciplinas/poo/slides/aula10_threads.pdf ·...

Post on 16-Nov-2018

215 views 0 download

Transcript of Programação Orientada a Objetos (DPADF 0063)bruno/disciplinas/poo/slides/aula10_threads.pdf ·...

Programação Orientada a Objetos

(DPADF 0063) Aula 10 – Threads

Universidade Federal de Santa Maria

Colégio Agrícola de Frederico Westphalen

Curso Superior de Tecnologia em Sistemas para Internet

Prof. Bruno B. Boniati – www.cafw.ufsm.br/~bruno

Linhas de execução independentes

Programação Concorrente

• Programar de forma concorrente significa pensar e escrever

programas de computador que fazem mais de uma coisa ao

mesmo tempo;

• Na vida real, muito do que fazemos (respirar, falar, ler, escutar)

e do que os computadores fazem (compilar, imprimir, tocar

música, baixar vídeo) acontece de forma concorrente.

• Os objetivos da programação concorrente podem ser:

▫ Reduzir o tempo total de processamento

▫ Aumentar a confiabilidade disponibilidade das aplicações

▫ Especializar serviços (SOs, simuladores), etc.

Onde utilizar programação concorrente?

• Programação Reativa ▫ A aplicação responde a eventos de entrada (em uma GUI cada

evento corresponde a uma ação).

• Programação Interativa ▫ Escrever um programa dividindo tarefas: uma tarefa para fazer

alguma interação com o usuário, outra para exibir mensagens,

outra para fazer animação, etc...

• Paralelismo físico/distribuição ▫ Permite tirar vantagem de múltiplas CPUs centralizadas ou

distribuídas (cada vez mais presentes nos sistemas

computacionais).

Threads

• Uma thread é uma linha de execução, um fluxo de execução,

um segmento de programa executando dentro da CPU;

• Programação multithreading consiste em pensar e escrever as

aplicações de forma que ações sejam executadas em paralelo

de forma concorrente;

• Apenas computadores com múltiplos processadores podem de

fato executar instruções em paralelo, porém, sistemas

operacionais modernos oferecem recursos para criar uma

“ilusão” de paralelismo em máquinas monoprocessadas

compartilhando o tempo de execução entre diferentes

aplicações.

Threads (cont.)

• A figura abaixo demonstra a utilização da CPU por segmentos

de código. Observe que apenas um segmento ocupa a CPU de

cada vez e há uma fila de segmentos aguardando sua vez ...

Segmento

Segmento

Segmento

Segmento

Segmento

Segmento

Segmento

Segmento

CPU

Fila de

Segmentos

segmento

ganha a CPU

segmento deixa a CPU

Programação concorrente & Java

• Java disponibiliza a concorrência de forma nativa por meio da

linguagem e de seu framework;

• Ao especificar uma thread estamos criando um fluxo de

execução independente, com sua própria pilha de chamadas de

método e contador de programa;

• Java inclui primitivos de multithreading de forma integrada à

linguagem de programação, não sendo necessário chamar

primitivas específicas do sistema operacional (isso é muito

importante para a portabilidade do código).

• A classe Thread representa um fluxo de execução

independente para a linguagem Java.

Um pequeno exemplo ...

• Compile o código abaixo e o execute a classe resultante ...

public class Corrida extends Thread {

public Corrida(String n) {

super(n);

}

public void run() {

for (int i=1; i<=10; i++) {

System.out.println(getName() + " na volta " + i);

}

System.out.println("-->" + getName() + " chegou ");

}

public static void main (String args[]) {

Corrida s = new Corrida("Sebastian Vettel");

Corrida f = new Corrida("Fernando Alonso");

Corrida k = new Corrida("Kimi Raikkonen");

s.start();

f.start();

k.start();

}

}

Quem ganhou a corrida ? (execute novamente ... e agora, quem ganhou?)

O método run()

contém a tarefa que

a thread deve

executar. Quando

ele termina a thread

termina

O método start() coloca a thread

na fila para tentar ganhar CPU

Métodos de uma thread

• getName()

▫ Cada thread pode opcionalmente ser identificada por um nome.

• setPriority(int prioridade)

▫ As threads podem ter prioridades distintas entre si (baixa, média e alta)

• sleep(long tempo)

▫ Faz com que a thread descanse por alguns milissegundos deixando

de ocupar a CPU. Ao acordar ela volta para a fila.

• start()

▫ Coloca a thread na fila para tentar ganhar CPU.

• yield()

▫ Retira a thread da CPU e já a recoloca na fila novamente (isso dá

chance de uma outra thread ganhar a CPU).

Ciclo de vida de uma thread

running

(executando) sleeping

(dormindo)

waiting

(aguardando

algum recurso) runnable

(aguardando

na fila da CPU)

Monitor

Waiting

Dead

(Encerrada)

O que leva a thread deixar a CPU?

• O objetivo de uma thread é alcançar a CPU para que possa ser

executada, mas há situações onde a thread deixa a CPU:

▫ Voluntariamente (através do método yield() para dar chance a outras

threads ocuparem o processador);

▫ Quando ela está aguardando algum recurso de IO (dados de arquivo,

entrada via teclado);

▫ Quando ela está aguardando lock de um recurso compartilhado;

▫ Por preempção (alguns SOs dão uma fatia de tempo para cada thread);

▫ Por prioridade (quando alguma thread de maior prioridade aparece na fila);

▫ Ao terminar sua tarefa (quando o método run() conclui).

Prioridade de uma thread

• O modelo de escalonamento de Threads da JVM (a forma

como uma Thread é elegida para utilizar a CPU) é baseada em

prioridades.

• Existem 3 prioridades que podem ser utilizadas:

▫ Thread.MIN_PRIORITY (prioridade baixa)

▫ Thread.MAX_PRIORITY (prioridade alta)

▫ Thread.NORM_PRIORITY (prioridade normal)

• O método setPriority() é utilizado para indicar qual é a

prioridade da Thread.

Prioridade de uma thread (cont.)

• A JVM elege as Threads de maior prioridade para ocupar a CPU em

detrimento das Threads de menor prioridade.

• Uma Thread de menor prioridade pode será executada quando as

Threads de maior prioridade

▫ terminarem

▫ executarem “yield()”

▫ executarem “sleep()”

▫ ficarem aguardando algum recurso bloqueado

• Se uma Thread de prioridade alta ocupar a CPU por muito tempo a

JVM pode selecionar, ocasionalmente, uma Thread de prioridade

mais baixa, “preemptando” a Thread de mais alta prioridade

Interface Runnable

• Criar uma classe descendente de Thread nem sempre é uma

boa ideia, principalmente em Java, onde cada classe tem uma

única superclasse.

• A classe Thread disponibiliza alguns métodos estáticos que

recebem objetos de classes que implementam uma interface

específica ... Runnable

• A interface Runnable exige basicamente a implementação do método run().

Interface Runnable (cont) class Tarefa implements Runnable {

String nome;

public Tarefa (String n) {

nome = n;

}

public void run() {

for (int i=0; i<10; i++)

System.out.println(nome + " executando tarefa (" + i + ")");

System.out.println(nome + " concluído ");

}

public static void main(String args[]) {

Tarefa t1 = new Tarefa("t1");

Tarefa t2 = new Tarefa("t2");

new Thread(t1).start();

new Thread(t2).start();

}

}

A interface Runnable

exige o método run()

Uma nova Thread é instanciada

passando como parâmetro um objeto de uma classe Runnable

Sincronização (Imagine o seguinte cenário)

• Uma classe conta bancária com métodos para obter e

atualizar o saldo;

public class Conta {

public double saldo = 0;

public Conta(double saldo) {

this.saldo = saldo;

System.out.println("Saldo inicial: R$" + saldo);

}

public double getSaldo() {return saldo;}

public void setSaldo(double saldo) {this.saldo = saldo;}

}

Sincronização (cont.) (Imagine o seguinte cenário)

• Um classe banco com dispositivos para

movimentar as contas;

public class Banco {

public boolean saque(Conta conta, double valor) {

double saldo = conta.getSaldo();

if (saldo < valor) {

System.out.println("Saldo insuficiente para o saque.");

return false;

}

double novoSaldo = saldo - valor;

System.out.println(Thread.currentThread().getName() +

" sacou R$" + valor +

". Saldo após saque: R$" + novoSaldo);

conta.setSaldo(novoSaldo);

return true;

}

}

Sincronização (cont.) (Imagine o seguinte cenário)

• Uma classe cliente de um banco com sua conta corrente,

um valor para saque e a vontade de gastar todo o dinheiro;

public class Cliente extends Thread {

private static Banco banco = new Banco();

private Conta conta = null;

private double valor = 1000;

public Cliente(String nome, Conta conta) {

super(nome);

this.conta = conta;

}

public void run() {

double total = 0;

while (banco.saque(conta,valor))

total += valor;

System.out.println(getName() + " sacou total de R$" + total);

}

}

Sincronização (cont.) (Imagine o seguinte cenário)

• Várias instâncias de clientes (uma família) movimentando

uma mesma conta ao mesmo tempo.

public class Familia {

public static void main (String args[]) {

final Conta conta = new Conta(100000);

Cliente pai = new Cliente("Pai ", conta);

Cliente mae = new Cliente("Mãe ", conta);

Cliente filho = new Cliente("Filho", conta);

pai.start();

mae.start();

filho.start();

}

}

Sincronização (cont.) (Observe a execução da classe família)

Saldo inicial: R$10000.0

Pai sacou R$1000.0. Saldo após saque: R$9000.0

Filho sacou R$1000.0. Saldo após saque: R$9000.0

Mãe sacou R$1000.0. Saldo após saque: R$9000.0

Filho sacou R$1000.0. Saldo após saque: R$8000.0

Pai sacou R$1000.0. Saldo após saque: R$8000.0

Filho sacou R$1000.0. Saldo após saque: R$7000.0

Mãe sacou R$1000.0. Saldo após saque: R$8000.0

Filho sacou R$1000.0. Saldo após saque: R$6000.0

Pai sacou R$1000.0. Saldo após saque: R$7000.0

Filho sacou R$1000.0. Saldo após saque: R$5000.0

Mãe sacou R$1000.0. Saldo após saque: R$7000.0

Filho sacou R$1000.0. Saldo após saque: R$4000.0

Pai sacou R$1000.0. Saldo após saque: R$6000.0

Filho sacou R$1000.0. Saldo após saque: R$3000.0

Mãe sacou R$1000.0. Saldo após saque: R$6000.0

Filho sacou R$1000.0. Saldo após saque: R$2000.0

Pai sacou R$1000.0. Saldo após saque: R$5000.0

Filho sacou R$1000.0. Saldo após saque: R$1000.0

Mãe sacou R$1000.0. Saldo após saque: R$5000.0

Filho sacou R$1000.0. Saldo após saque: R$0.0

Pai sacou R$1000.0. Saldo após saque: R$4000.0

Saldo insuficiente para o saque.

Mãe sacou R$1000.0. Saldo após saque: R$4000.0

Filho sacou total de R$10000.0

Pai sacou R$1000.0. Saldo após saque: R$3000.0

Mãe sacou R$1000.0. Saldo após saque: R$3000.0

Pai sacou R$1000.0. Saldo após saque: R$2000.0

Mãe sacou R$1000.0. Saldo após saque: R$2000.0

Pai sacou R$1000.0. Saldo após saque: R$1000.0

Mãe sacou R$1000.0. Saldo após saque: R$1000.0

Pai sacou R$1000.0. Saldo após saque: R$0.0

Saldo insuficiente para o saque.

Mãe sacou R$1000.0. Saldo após saque: R$0.0

Saldo insuficiente para o saque.

Pai sacou total de R$10000.0

Mãe sacou total de R$10000.0

Saldo inicial R$ 10.000,00

Saldo insuficiente para o saque

Mãe sacou R$ 1000,00 (saldo após o saque R$ 3 mil)

Quanto cada um sacou até que o saldo acabasse?

Filho sacou total de R$ 10000

Pai sacou total de R$ 10000

Mãe sacou total de R$ 10000

Sincronização (cont.)

• O código executado anteriormente utiliza um recurso compartilhado entre

todas as Threads (objeto da classe Conta);

• Para garantir acesso exclusivo a um recurso compartilhado, ou seja, criar

uma fila de espera para utilizar o recurso um de cada vez, faz-se necessário

utilizar recursos de sincronização.

• A palavra reservada synchronized marca um método ou um bloco de

código como sincronizado, isso garante que aquele código somente será

acessado por uma Thread de cada vez.

• Experimente alterar a interface dos métodos abaixo incluindo a palavra synchronized:

Conta.getSaldo()

Conta.setSaldo(double)

Banco.saque(Conta, double)

Exercícios para fixação

Corrida Maluca...

• Codifique uma classe veículo que implementa a interface runnable.

• A cada corrida, cada veículo possui uma velocidade que é sorteada quando a

classe é instanciada.

• Simule uma corrida maluca com os personagens do desenho animado

Wacky Races (produzido nos estúdios Hanna-Barbera nos anos de 68 e 70).

• Utilize todos os personagens (Dick Vigarista & Muttley, Irmãos Rocha,

Cupê Mal-Assombrado, Professor Aéreo, Barão Vermelho, Penélope Charmosa,

Carro Tanque, Quadrilha de Morte, Peter Perfeito e Rufus Lenhador)