Buscar

Aula 2.0 Programação Concorrente em Java (Threads)

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 107 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 107 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 107 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Programação Concorrente
Threads em Java
Prof. Jorge Viana Doria Junior, M.Sc.
Mestre em Informática DCC/IM/UFRJ
jjunior@unicarioca.edu.br
1
Sumário
Executar tarefas simultaneamente utilizando Threads.
Entender o escalonador de Threads.
Problemas com concorrência e sincronismos em Threads.
Estratégias para solução de problemas com Threads.
Exclusão Mútua
Locks
Objetos imutáveis
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.
Em várias situações, precisamos “rodar duas coisas ao mesmo tempo”. Imagine um programa que gera um relatório muito grande em PDF. É um processo demorado e, para dar alguma satisfação para o usuário, queremos mostrar uma barra de progresso. Queremos então gerar o PDF e ao mesmo tempo atualizar a barrinha. Pensando um pouco mais amplamente, quando usamos o computador também fazemos várias coisas simultaneamente: queremos navegar na internet e ao mesmo tempo ouvir música. A necessidade de se fazer várias coisas simultaneamente, ao mesmo tempo, paralelamente, aparece frequentemente na computação. Para vários programas distintos, normalmente o próprio sistema operacional gerencia isso através de vários processos em paralelo. Em um programa só (um processo só), se queremos executar coisas em paralelo, normalmente falamos de Threads.
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 Compartilhada
Ambiente de Execução próprio
Existe dentro de um Processo
Possui ao menos um thread
O conceito de thread está intimamente ligado ao conceito de processo, assim é fundamental entender o que são processos, como eles são representados e colocados em execução pelo Sistema Operacional, para em seguida entender as threads.
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:
 public class MeuPrograma {
	public static void main (String[] args) {
		GeraPDF gerapdf = new GeraPDF();
		Thread threadDoPdf = new Thread(gerapdf);
		threadDoPdf.start();
		BarraDeProgresso barraDeProgresso = new BarraDeProgresso();
		Thread threadDaBarra = new Thread(barraDeProgresso);
		threadDaBarra.start();
	}
 }
Porém, o código não compilará.
Como a classe Thread sabe que deve chamar o método public void rodar ( )? 
Como ela sabe que nome de método daremos e que ela deve chamar esse método especial?
Falta na verdade um contrato entre as nossas 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
Programa
Runnable
referência
Classe A
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:
No exercício 01, foi criada a classe GeradorRelatorio, que implementa Runnable;
No exercício 02, criamos uma Thread 8 que, ao iniciar(thread.start()), invoca o método GeradorRelatorio.run();
Qual o resultado gerado pelo próximo exercício?
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
Resposta: Porque as Threads agem como processos independentes.
Podemos solicitar a execução de vários processos ao mesmo tempo - Multithread;
Vamos criar uma Thread para “exibir” uma mensagem, enquanto o relatório é processado;
Exercícios práticos
4) Crie a classe BarraDeProgresso, que será executada por umaThread, 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.
Vimos que as Threads agem como processos independentes;
Podemos solicitar a execução de vários processos ao mesmo tempo - Multithread;
Vamos criar uma Thread para “exibir” uma mensagem, enquanto o relatório é processado;
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;
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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.
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execuçãodesta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
Escalonamento
Fonte: Java, Como programar. Deitel, 6ª Edição
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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.
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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);
	 }
 }
 }
}
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essas escolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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;
O problema é que no computador existe apenas um processador capaz de executar coisas. E quando queremos executar várias coisas ao mesmo tempo, e o processador só consegue fazer uma coisa de cada vez? Entra em cena o escalonador de threads. O escalonador (scheduler), sabendo que apenas uma coisa pode ser executada de cada vez, pega todas as threads que precisam ser executadas e faz o processador ficar alternando a execução de cada uma delas. A ideia é executar um pouco de cada thread e fazer essa troca tão rapidamente que a impressão que fica é que as coisas estão sendo feitas ao mesmo tempo. O escalonador é responsável por escolher qual a próxima thread a ser executada e fazer a troca de contexto (context switch). Ele primeiro salva o estado da execução da thread atual para depois poder retomar a execução da mesma. Aí ele restaura o estado da thread que vai ser executada e faz o processador continuar a execução desta. Depois de um certo tempo, esta thread é tirada do processador, seu estado (o contexto) é salvo e outra thread é colocada em execução. A troca de contexto é justamente as operações de salvar o contexto da thread atual e restaurar o da thread que vai ser executada em seguida. Quando fazer a troca de contexto, por quanto tempo a thread vai rodar e qual vai ser a próxima thread a ser executada, são escolhas do escalonador. Nós não controlamos essasescolhas (embora possamos dar “dicas” ao escalonador). Por isso que nunca sabemos ao certo a ordem em que programas paralelos são executados. Você pode pensar que é ruim não saber a ordem. Mas perceba que se a ordem importa para você, se é
importante que determinada coisa seja feita antes de outra, então não estamos falando de execuções paralelas, mas sim de um programa sequencial normal (onde uma coisa é feita depois da outra, em uma sequência). Todo esse processo é feito automaticamente pelo escalonador do Java (e, mais amplamente, pelo escalonador
do sistema operacional). Para nós, programadores das threads, é como se as coisas estivessem sendo executadas ao mesmo tempo.
E mais de um processador?
A VM do Java e a maioria dos SOs modernos consegue fazer proveito de sistemas com vários processadores ou multi-core. A diferença é que agora temos mais de um processador executando coisas e teremos, sim, execuções verdadeiramente paralelas. Mas o número de processos no SO e o número de threads paralelas costumam ser tão grandes que, mesmo com vários processadores, temos as trocas de contexto. A diferença é que o escalonador tem dois ou mais processadores para executar suas threads. Mas dificilmente terá uma máquina com mais processadores que threads paralelas executando.
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.
36
36
36
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.
37
37
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:
8) Qual o resultado?
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();
 }
}
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.
É mais fácil criar uma classe filha de Thread do que que usar um objeto Runnable.
Mas não é uma boa prática estender uma Thread.
Com extends Thread, nossa classe ficaria muito limitada, não podendo herdar os componentes de outra.
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.
Sincronização entre threads em Java
Problema: inconsistência de dados.
Em outras palavras...
Condição de corrida: operações sobre recursos compartilhados que podem levar a inconsistências dependendo da ordem de execução.
Seção crítica: trecho do programa que contém operações que podem levar a inconsistências
Solução: exclusão mútua
Em outras palavras...
Garantir atomicidade e acesso exclusivo ao recurso na seção crítica.
Condições de Corrida
Threads acessam uma variável compartilhada ao mesmo tempo.
As threads escrevem e lêem a variável.
O resultado de uma thread pode sobrescrever o da outra.
O resultado da computação pode variar.
48
48
Race conditions é possível quando threads compartilham dados, lendo, escrevendo concorrentemente, e o resultado final pode variar, depende de quando a thread vai fazer o que. O resultado pode ser correto ou não. Para evitar isso temos que usar estratégias de sincronização.
48
Condições de corrida:
Exemplo
Thread T1: Pega o valor .
Thread T2: Pega o valor .
Thread T1: Incrementa o valor; resultado: 1.
Thread T2: Decrementa o valor; resultado: -1
Thread T1: Armazena o resultado: 1.
Thread T2: Armazena o resultado: -1.
49
49
Thread T1 chama o incrementar e Thread T2 chama o decrementar ao mesmo tempo, se o valor inicial do contador é zero, as ações intervaladas podem ser a sequência listada no slide. Por essa sequência, o resultado da Thread T1 é perdido. Essa sequência é uma das possíveis, podendo em outras circunstância, o resultado de T2 que pode ser Perdido.
49
Exemplo:
código não sincronizado 
Entre a leitura e o armazenamento, a thread atual pode ser interrompida!
Operação não-atômica!
Valor de contador é lido (possivelmente guardado em um registrador)
Valor lido é incrementado e armazenado (na memória)
50
50
Cada invocação do incrementar deve adicionar 1 ao contador e do decrementar deve subtrair 1.Porém se o objeto Contador for referenciado pro múltiplas threads, a interferência entre as threads pode fazer que esse comportamento não seja o esperado.
 
50
Seção Crítica (Região Crítica)
Em programação concorrente, uma região crítica é uma área de código de um algoritmos que acessa um recurso compartilhado que não pode ser acessado concorrentemente por mais de uma linha de execução.
Ocorre quando:
Sistema composto por N processos (onde N > 1).
Cada processo executa seu próprio código independente dos demais.
Processos compartilham um dado recurso.
O estado de um processo é desconhecido pelo outro.
O estado/valor do recurso pode ser alterado.
O objetivo é tornar a operação sobre o recurso compartilhado atômica, ou seja, Transação Atômica, em ciência da computação, é uma operação, ou conjunto de operações, em uma base de dados ou em uma estrutura de dados ou em qualquer outro sistema computacional, que deve ser executada completamente em caso de sucesso, ou ser abortada completamente em caso de erro. O exemplo clássico para a necessidade de uma "transação atômica" é aquele da transferência entre duas contas bancárias. No momento de uma transferência de valores de uma conta "A" para uma conta "B", que envolve pelo menos uma operações de ajuste no saldo para cada conta, se o computador responsável pela operação é desligado por faltade energia, espera-se que o saldo de ambas as contas não tenha se alterado. Neste caso são utilizados sistemas que suportam transações atômicas.
Seção Crítica: Exemplo
public void T0 ( ) {
		long i;
		for (i=0; i<1000000; i++) {
			a = a + 5;
		}
		System.out.println(“Encerrei a T0”);
}
public void T1 ( ) {
		long i;
		for (i=0; i<1000000; i++) {
			a = a + 2;
		}
		System.out.println(“Encerrei a T1”);
}
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Seção Crítica: Exemplo
Uma estratégia simples de prevenção de interferência de threads e erros de consistência de memória.
Campos final, que não podem ser modificados após a construção do objeto, podem ser lidos de maneira segura.
Sincronização entre threads em Java
59
59
Métodos sincronizados permitem uma estratégia simples de prevenção de interferência de threads e erros de consistência na memória.
Por exemplo, se um objeto é visível por mais de uma thread, toda leitura ou escrita nas variáveis deste objeto serão feitas através de métodos sincronizados.
Uma importante exceção é que campos “final”, que não podem ser modificados após a construção do objeto, podem ser lidos de maneira segura, não precisando de métodos sincronizados.
59
Sincronização entre threads em Java
Java oferece 2 formas básicas de sincronização:
Métodos sincronizados
Blocos sincronizados
Métodos sincronizados
Através da palavra synchronized 
61
61
Reduzir o custo de sincronização associado ao método.
Blocos sincronizados
62
62
62
Blocos sincronizados
Poder combinar mais de um objeto no momento da sincronização.
63
63
Sincronização entre threads em Java
Competição no acesso a dados compartilhados.
Variável de classe
(STATIC)
e/ou
Referência para um mesmo objeto
Sincronização entre threads em Java
Situação típica: duas ou mais threads fazem operação sobre dado compartilhado.
Sincronização entre threads em Java
Situação 1: T1 primeiro e T2 depois.
Situação 1: Não há problemas de inconsistência...
Sincronização entre threads em Java
Situação 2: T1 e T2 concorrentemente.
Situação 2: Há problemas de inconsistência...
Exclusão Mútua em Java
Compartilhamento do objeto.
 public static void main(String[] args) {
	
	ContaBancaria c = new ContaBancaria(1000f);
	ThreadDeposita td = new ThreadDeposita(c);
	ThreadRetira tr = new ThreadRetira(c);
	td.start();
	tr.start();
 }
Exclusão Mútua em Java
Thread faz acesso ao objeto.
	public class ThreadDeposita extends Thread {
		private ContaBancaria c;
		public ThreadDeposita(ContaBancaria c) {
			this.c = c;
		}
		public void run() {
			for (int i = 0; i < 5; i++)
			 c.deposita(300f);
		}
	}
Exclusão Mútua em Java
Thread faz acesso ao objeto.
	public class ThreadRetira extends Thread {
		private ContaBancaria c;
		public ThreadRetira(ContaBancaria c) {
			this.c = c;
		}
		public void run() {
			for (int i = 0; i < 5; i++)
			 c.retira(100f);
		}
	}
Exclusão Mútua em Java
Sem synchronized.
	
	class ContaBancaria {
		private float saldo;
		public ContaBancaria(float v) { saldo = v; }
		public float getSaldo() { return saldo; }
		void deposita(float v) {
			saldo += v; 
			System.out.println("Depósito: R$ 300,00 - Saldo: " + getSaldo());}
		void retira(float v) {
			saldo -= v; 
			 System.out.println("Retirada: R$ 100,00 - Saldo: " + getSaldo());}
	}
Exclusão Mútua em Java
Uso de synchronized.
	
	class ContaBancaria {
		private float saldo;
		public ContaBancaria(float v) { saldo = v; }
		public float getSaldo() { return saldo; }
		synchronized void deposita(float v) {
			saldo += v; 
			System.out.println("Depósito: R$ 300,00 - Saldo: " + getSaldo());}
		synchronized void retira(float v) {
			saldo -= v; 
			 System.out.println("Retirada: R$ 100,00 - Saldo: " + getSaldo());}
	}
Métodos
synchronized
executam
em exclusão mútua
sobre o mesmo objeto
compartilhado
Exclusão Mútua em Java
Métodos synchronized em um mesmo objeto são executados em exclusão mútua.
Só têm efeito em objetos compartilhados (mais de 1 thread referenciando mesmo objeto).
Limitação à concorrência.
Usar com cuidado.
Monitores
São objetos que garantem a exclusão mútua na execução dos procedimentos associados a eles.
Apenas um procedimento associado ao monitor pode ser executado em um determinado momento.
Em Java todo objeto possui um monitor associado. 
74
74
Monitores
O conceito de monitor foi proposto por C. A. R. Hoare em 1974 e pode ser encarado como um objeto que garante a exclusão mútua na execução dos procedimentos a ele associados. Ou seja, apenas um procedimento associado ao monitor pode ser executado em um determinado momento. Por exemplo, suponha que dois procedimentos A e B estão associados a um monitor. Se no momento da invocação do procedimento A algum o procedimento B estiver sendo executando o processo ou thread que invocou o procedimento A fica suspenso até o término da execução do procedimento B. Ao término do procedimento B o processo que invocou o procedimento A é "acordado" e sua execução retomada. O uso de monitores em Java é uma variação do proposto por Hoare. Na linguagem Java todo objeto possui um monitor associado. Para facilitar o entendimento podemos encarar o monitor como um detentor de um "passe". Toda thread pode pedir "emprestado" o passe ao monitor de um objeto antes de realizar alguma computação. Como o monitor possui apenas um passe, apenas um thread pode adquirir o passe em um determinado instante. O passe tem que ser devolvido para o monitor para possibilitar o empréstimo do passe a outro thread. 
Tentar fazer um desenho explicando isso:
suponha que dois procedimentos A e B estão associados a um monitor. Se no momento da invocação do procedimento A algum o procedimento B estiver sendo executando o processo ou thread que invocou o procedimento A fica suspenso até o término da execução do procedimento B. Ao término do procedimento B o processo que invocou o procedimento A é "acordado" e sua execução retomada. 
74
Sincronização usando Monitores
Declarando uma seção de código sincronizada
75
75
75
Sincronização usando Monitores
Declarando o método todo sincronizado.
76
76
76
Sincronização usando Monitores
Código com problemas no uso de monitores
Sincronização usando Monitores
Código corrigido:
Sincronização usando Monitores
Faz uso dos métodos Wait() e notify() para gerar eventos de espera e notificação para parar e esperar.
Erros de consistência na memória
Quando as threads possuem diferentes visões de um determinado dado.
Exemplo:
Uma variável inteiro compartilhada entre as threads
int contador = 0;
Thread A incrementa a variável
contador++;
Thread B imprime a variável
System.out.println(contador);
80
80
Se os dois métodos fossem chamados na mesma thread em sequência, é seguro que o valor impresso seria 1, porém em threads diferente não é garantido que no momento da impressão o valor do contador já tenha sido incrementado pela outra thread;
80
Modificador volatile
Variável pode ser acessada/modificada por mais de uma thread.
O valor da variável nunca será guardado localmente pela thread.
Garante que escrita e leitura são realizadas diretamente na memória principal.
81
81
81
Quando usar?
Para escrever em uma variável, como uma flag, em uma thread.
Para checar essa variável em outra thread.
O valor de escrita não depende do valor corrente.
Não há preocupações em perder o valor atualizado.
82
82
82
Para campos que são imutáveis (final).
Para variáveis que são acessadas por uma única thread.
Para operações complexas, como quando é necessário prevenir acesso a uma variável por um certo tempo.
Quando NÃO usar?
83
83
83
Exemplo
84
84
Deadlock
Descreve uma situação em que dois ou mais Threads são bloqueados para sempre esperando um ao outro.
Ocorre quando várias threads necessitamdos mesmos bloqueios, mas os obtém em ordem diferente.
Nas Multithreads em java podem ocorrer deadlocks porque a palavra-chave syncronized causa o bloqueio da thread em execução, enquanto aguarda o bloqueio associado ao objeto especificado.
85
85
Deadlock - Exemplo
public class TestDeadlockExample1 {  
  public static void main(String[] args) {
 final String resource1 = "ratan jaiswal";  
    final String resource2 = "vimal jaiswal";  
    // t1 tries to lock resource1 then resource2  
    Thread t1 = new Thread() {  
      public void run() {  
          synchronized (resource1) {  
           System.out.println("Thread 1: Holding resource 1");  
           try { 
	 Thread.sleep(10);
 } catch (InterrupedException e) { }
	System.out.println("Thread 1: Waiting for resource 2");   
           synchronized (resource2) {  
             System.out.println("Thread 1: Holding resources 1 & 2");  
           }  
         }  
      }  
    };
86
86
Deadlock - Exemplo
 // t2 tries to lock resource2 then resource1  
    Thread t2 = new Thread() {  
      public void run() {  
        synchronized (resource2) {  
          System.out.println("Thread 2: Holding resource 2");
          try {
 Thread.sleep(10);
 } catch (InterruptedException e) { }
 System.out.println("Thread 2: Waiting for resource 1");
          synchronized (resource1) {  
            System.out.println("Thread 2: Holding resource 1 & 2");  
          }  
        }  
      }  
    };  
        
    t1.start();  
    t2.start();  
  }  
}  
Thread 1: Holding resource 1...
Thread 1: Waiting for resource 2...
Thread 1: Holding resource 1 & 2...
Thread 2: Holding resource 1...
Thread 2: Waiting for resource 2...
Thread 2: Holding resource 1 & 2... 
87
87
Deadlock - Solução
 // t2 tries to lock resource2 then resource1  
    Thread t2 = new Thread() {  
      public void run() {  
         synchronized (resource1) {  
          System.out.println("Thread 2: Holding resource 1");
          try {
 Thread.sleep(10);
 } catch (InterruptedException e) { }
 System.out.println("Thread 2: Waiting for resource 2");
          synchronized (resource2) {  
            System.out.println("Thread 2: Holding resource 1 & 2");  
          }   
        }  
      }  
    };  
        
    t1.start();  
    t2.start();  
  }  
}  
Então, apenas modifique a ordem dos RESOURCES e das mensagens. Isso impede que o programa entre em deadlock. 
Thread 1: Holding resource 1...
Thread 1: Waiting for resource 2...
Thread 1: Holding resource 1 & 2...
Thread 2: Holding resource 1...
Thread 2: Waiting for resource 2...
Thread 2: Holding resource 1 & 2... 
88
88
Locks
A interface Lock pode ser usada no lugar de blocos sincronizados .
Oferecem maior flexibilidade.
Para criar um lock explícito, você instancia uma implementação da interface Lock.
Normalmente, instancia-se ReentrantLock
Para obter o lock, usa-se o método lock()
Para liberar o lock, usa-se unlock()
89
89
Locks can be used in place of synchronized blocks and to implement synchronized methods, that is, use 
Locks offer more flexibility than synchronized blocks in that a thread can unlock multiple locks it holds in a different order than the locks were obtained. This cannot be done with the implied locks of synchronized blocks because synchronized blocks must be lexically nested. 
Para criar um lock explícito, você instancia uma implementação da interface Lock Normalmente, instancia-se ReentrantLock
Para obter o lock, usa-se o método lock() Para liberar o lock: usa-se unlock() Já que o lock não é liberado automaticamente no final de um método, pode ser útil usar try/finally para garantir que o lock seja liberado
89
 public class Example {
	private Lock aLock = new ReentrantLock();
	public int get(int who) {
		aLock.lock();
		try{
			//Acessar recursos compartilhados...
		} finally {
			aLock.unlock();
		}
	}
}
Locks: Sintaxe Básica
90
90
Como garantir que o lock seja sempre liberado?
Já que o lock não é liberado automaticamente, é essencial que o corpo do método esteja coberto por try/finally para garantir que o lock seja liberado
Locks
91
91
91
Variáveis condicionais
Para esperar por um lock explícito, cria-se uma variável condicional.
Um objeto que implementa a interface Condition.
Usar Lock.newCondition() para criar uma condição
A condição provê os métodos:
await() para esperar até a condição ser verdadeira
signal() e signalAll() para avisar aos threads que a condição ocorreu.
92
92
Para esperar por um lock explícito, cria-se uma variável condicional Um objeto que implementa a interface Condition
Usar Lock.newCondition() para criar uma condição
Uma variável condicional não é um lock, mas sim uma variável associada a um lock. Variáveis condicionais são usadas no contexto de sincronização de dados.
Variáveis condicionais geralmente tem uma API que possuem as mesma funcionalidades que os mecanismo de Java de esperar e notificar.
Nestes mecanismos, as variáveis condicionais são na verdade o objeto lock que está protegendo. 
92
Exemplo
Blocos Guardados
(Guarded Blocks)
Thread frequentemente têm que coordenar suas ações.
Guarded blocks é um mecanismo comum para coordenação.
Um bloco só é executado caso uma determinada condição seja verdadeira.
94
94
Blocos Guardado com eficiência
95
Nota: sempre chame wait dentro de um loop que testa pela condição que está sendo esperada. Não assuma que a interrupção foi pela pela condição particular que você estava esperando, ou que a condição ainda seja verdadeira
Note: Always invoke wait inside a loop that tests for the condition being waited for. Don't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true.
95
Notificando as threads
Notificar é retirar do estado de espera.
notify(): Notifica uma thread que esteja esperando em um lock.
Não se especifica qual thread vai ser notificada.
nofityAll(): Notifica todas as threads que estejam esperando em um lock.
96
96
Quando usar notify()?
Acordar uma única thread.
Não é possível especificar qual thread é acordada.
Útil para sistemas massivamente paralelo:
Grande número de threads.
Todas fazendo tarefas similares.
Então, não importa qual é acordada.
97
Notify, which wakes up a single thread. Because notify doesn't allow you to specify the thread that is woken up, it is useful only in massively parallel applications — that is, programs with a large number of threads, all doing similar chores. In such an application, you don't care which thread gets woken up.
97
Alguns detalhes 
Notificações: 
Devem ser usados na implementação das operações dos objetos sincronizados.
Evitando Starvation:
Certifique que as threads não fiquem em espera eternamente.
Notifique-as! 
98
Assim como o wait Esses métodos devem ser usados na implementação das operações dos objetos sincronizados, ou seja, nos métodos synchronized dos objetos sincronizados. Pois só faz sentido uma thread notificar ou esperar por outra em objetos sincronizados e o Sistema de Execução Java tem mecanismos para saber qual thread irá esperar ou receber a notificação, pois somente uma thread por vez pode estar executando um método sincronizado de um objeto compartilhado.
Don't forget to ensure that the application has other threads which acquire locks on the objects (on which few other threads have previously called wait() method) and call either notify() and notifyAll() methods so that the application can avoid startvation of those threads which have called wait() on the objects under consideration. notifyAll() scheduled all the waiting threads whereas notify()randomly picks one of the waiting threads and schedules it. Use the method which suits the design of your application. Read more aout the differences betweenthe two methods in this article.
98
Exemplo de fluxo de notificação
Thread A invoca o método wait().
O lock é liberado.
Execução suspensa.
Outra thread adquire o mesmo lock.
Ela invoca o notifyAll() .
Notifica todas as threads que esperam pelo lock.
99
Exemplo de Fluxo
100
Objetos imutáveis
O estado do objeto não pode ser alterado após sua construção.
São muito úteis em aplicações concorrentes.
Não são corrompidos por interferência de threads.
São consistentes.
101
An object is considered immutable if its state cannot change after it is constructed. Maximum reliance on immutable objects is widely accepted as a sound strategy for creating simple, reliable code. Immutable objects are particularly useful in concurrent applications. Since they cannot change state, they cannot be corrupted by thread interference or observed in an inconsistent state. 
Programmers are often reluctant to employ immutable objects, because they worry about the cost of creating a new object as opposed to updating an object in place. The impact of object creation is often overestimated, and can be offset by some of the efficiencies associated with immutable objects. These include decreased overhead due to garbage collection, and the elimination of code needed to protect mutable objects from corruption. 
The following subsections take a class whose instances are mutable and derives a class with immutable instances from it. In so doing, they give general rules for this kind of conversion and demonstrate some of the advantages of immutable objects. 
101
Estratégia de definição de um objeto imutável
Não fornecer os métodos “set”.
Fazer todos os atributos como final e private.
Não permitir subclasses sobrescrever métodos.
Declarando a classe como final.
Fazer o construtor privado e construir instancias em fábricas de métodos.
102
Don't provide "setter" methods — methods that modify fields or objects referred to by fields. 
Make all fields final and private. 
Don't allow subclasses to override methods. The simplest way to do this is to declare the class as final.
A more sophisticated approach is to make the constructor private and construct instances in factory methods. 
102
Caso algum atributo seja uma instância de um objeto mutável, não permitir que esses objetos sejam mudados:
Não fornecer métodos que modifica os objetos mutáveis.
Se necessário, crie cópias e armazene as referências para as cópias.
Estratégia de definição de um objeto imutável
103
If the instance fields include references to mutable objects, don't allow those objects to be changed: 
Don't provide methods that modify the mutable objects. 
Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods. 
103
Classe Imutável: Exemplo
104
Exercícios práticos
Crie um classe ContadorTempo que possui um atributo tick inteiro e um método nextTick()
Crie um classe Relogio que possui um contadorTempo e atraves de uma thread chama o metodo nextTick a cada segundo
Crie um classe Cronometro que herda de ContadorTempo e através de uma thread chama o nextTick()
Exercícios práticos
Implementar os exemplos do link abaixo, inclusive os 3 exercícios propostos ao final.
http://docs.oracle.com/javase/tutorial/essential/concurrency/
Referências
Java - Como programar, de Harvey M. Deitel, 8ª edição.
Use a cabeça! - Java, de Bert Bates e Kathy Sierra.
Effective Java Programming Language Guide, de Joshua Bloch.
http://docs.oracle.com/javase/tutorial/essential/concurrency/

Outros materiais