Baixe o app para aproveitar ainda mais
Prévia do material em texto
Programação Concorrente Threads em Java Prof. Antonio Felipe Podgorski Bezerra M.sC afbezerra@unicarioca.edu.br Sumário Estratégias para solução de problemas com Threads. - Exclusão Mútua - Locks - Objetos imutáveis 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. 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. Exemplo: código não sincronizado Entre a leitura e o armazenamento, a thread atual pode ser interrompida! Operação não-atômica! 1.Valor de contador é lido (possivelmente guardado em um registrador) 2.Valor lido é incrementado e armazenado (na memória) 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. 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 Sincronização entre threads em Java Java oferece duas formas básicas de sincronização: Métodos sincronizados Blocos sincronizados Métodos sincronizados Através da palavra synchronized Reduzir o custo de sincronização associado ao método. Blocos sincronizados Blocos sincronizados Poder combinar mais de um objeto no momento da sincronização. 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. Sincronização usando Monitores Declarando uma seção de código sincronizada Sincronização usando Monitores Declarando o método todo sincronizado. 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); 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. 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. 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? Exemplo https://www.youtube.co m/watch?v=Ult9o4b0y9s Threads stop using volatile variable flags 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 necessitam dos 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. 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"); } } } }; 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... 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... 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() 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 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 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. 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. Blocos Guardado com eficiência 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. 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. 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! 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. Exemplo de Fluxo 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. 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. 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 Classe Imutável: Exemplo 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/concu rrency/ 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/
Compartilhar