Upload
bianca-dantas
View
2.843
Download
0
Embed Size (px)
DESCRIPTION
Slides da aula sobre programação usando múltiplas threads em Java.
Citation preview
Linguagem de Programação III
Multithreading
Professora: Bianca de Almeida Dantas
E-mail: [email protected]
Site: www.biancadantas.com
TÓPICOS
• Threads
• Ciclo de vida
• Agendamento
• Criação e execução
• Exemplo
• Produtor-Consumidor
• Multithreading com GUI
Essa aula e seus exemplos foram baseados no
capítulo 26 do livro Java - Como Programar, 8ª
Edição (Deitel & Deitel)
THREADS
• Linhas de execução dentro de um processo. Conhecidas como processos leves.
• Cada thread possui sua pilha de chamadas de métodos e contador de instruções, entretanto a troca de contexto entre threads de um mesmo processo é menos custosa.
• Permite a execução concorrente de trechos de código que independem entre si.
• Se mais de um núcleo de processamento estiver disponível, a execução pode ser paralela.
• Linhas de execução dentro de um processo. Conhecidas como processos leves.
• Cada thread possui sua pilha de chamadas de métodos e contador de instruções, entretanto a troca de contexto entre threads de um mesmo processo é menos custosa.
• Permite a execução concorrente de trechos de código que independem entre si.
• Se mais de um núcleo de processamento estiver disponível, a execução pode ser paralela.
• Linhas de execução dentro de um processo. Conhecidas como processos leves.
• Cada thread possui sua pilha de chamadas de métodos e contador de instruções, entretanto a troca de contexto entre threads de um mesmo processo é menos custosa.
• Permite a execução concorrente de trechos de código que independem entre si.
• Se mais de um núcleo de processamento estiver disponível, a execução pode ser paralela.
THREADS – Ciclo de Vida
• Novo: a thread acaba de ser criada e ainda não foi iniciada pela primeira vez.
• Executável: a thread está realizando o seu processamento ou pronta para executá-lo.
• Espera: a thread está esperando que outra thread realize uma tarefa. Volta para o estado executável apenas quando outra thread a notifica.
• Espera sincronizada: a thread está esperando por um intervalo máximo de tempo.
• Bloqueado: a thread tentou realizar uma tarefa que não pôde ser imediatamente completada, ela fica neste estado até que a tarefa seja completada e, então, transita para o estado executável.
• Terminado: uma thread entra neste estado quando finaliza seu processamento (com ou sem sucesso).
• O estado executável pode ser subdividido em dois estados separados operacionalmente: pronto e em execução.
• O SO oculta a diferença entre esses dois estados da JVM, que os vê unicamente como executáveis.
• Quando uma thread é criada e passa para o estado executável, ela está, na verdade, no estado pronto; quando ela está efetivamente executando, ela está no estado em execução.
• Despachar uma thread: quando uma thread tem um processador atribuído a ela.
• Geralmente, uma thread executa por um pequeno período de tempo, o chamado quantum ou fração de tempo.
• Quando o quantum termina, a thread passa novamente para o estado pronto.
• Agendamento de threads é o processo que o sistema operacional utiliza para definir a ordem de execução de threads.
• O agendador de threads (thread scheduler), em geral, utiliza as prioridades associadas às threads para definir sua política de execução.
• Em Java, as prioridades variam entre MIN_PRIORITY (uma constante de 1) e MAX_PRIORITY (uma constante de 10). Threads, geralmente, são iniciadas com NORM_PRIORITY.
• As três constantes são definidas na classe Thread.
THREADS – Agendamento
.......
• Para especificar uma classe cujo código pode ser executado concorrentemente em uma aplicação, pode-se utilizar a interface Runnable.
• As threads podem ser instanciadas passando como argumento um objeto Runnable.
THREADS – Criação e Execução
EXEMPLO • Código do projeto Multithread2 enviado por e-
mail....
pool-1-thread-1 escreveu o valor 1 em 0
Próximo índice = 1
pool-1-thread-2 escreveu o valor 11 em 0
Próximo índice = 2
pool-1-thread-1 escreveu o valor 2 em 1
Próximo índice = 3
pool-1-thread-2 escreveu o valor 12 em 2
Próximo índice = 4
pool-1-thread-1 escreveu o valor 3 em 3
Próximo índice = 5
pool-1-thread-2 escreveu o valor 13 em 4
Próximo índice = 6
Conteúdo do vetor:
[11, 2, 12, 3, 13, 0]
• Como as threads executam independentemente e compartilham um vetor, o resultado pode não ser como desejado.
• Pela saída anterior, vimos que a thread 2 sobrescreveu o valor recém escrito pela thread 1.
• Situações como essa, nas quais não conseguimos garantir que o resultado será coerente e nas quais o resultado dependerá da ordem em que as threads serão escalonadas são chamadas de condições de corrida.
• O trecho de código no qual as diferentes threads acessam as variáveis compartilhadas (possivelmente, modificando-as) é chamado de seção crítica.
• Resumindo: o código visto não é seguro para threads.
• Para resolver o problema, transformaremos os trechos de acesso às variáveis compartilhadas em operações atômicas, impedindo que mais de uma thread execute tais instruções simultaneamente.
EXEMPLO • Código do projeto Multithread2 alterado enviado
por e-mail.
• Nesse código, fizemos com que o código do método que adiciona um elemento ao SimpleArray seja atômico. Em Java, isso é feito usando a palavra-chave synchronized.
• Quando uma thread está executando o corpo de um método synchronized, nenhuma outra pode executá-la antes da primeira terminar sua execução.
• A instrução synchronized também pode ser utilizada para trechos de código.
PRODUTOR-CONSUMIDOR • Um problema clássico da computação que envolve
processos (ou threads) que produzem recursos para serem consumidos por outros.
• Existe um buffer compartilhado para armazenamento dos recursos. O produtor precisa parar de produzir quando o buffer estiver cheio e o consumidor não pode consumir se não houver recurso disponível.
ABORDAGEM 1
• Utilizar um buffer compartilhado com apenas uma posição (classe BufferSemSincronizacao).
• Não há garantia sobre a ordem de execução das threads, logo, muitas vezes valores são produzidos com o buffer lotado ou, ainda, consumidos antes da produção, levando a resultados inconsistentes.
ABORDAGEM 2 • Utilizar um buffer compartilhado com apenas uma
posição, mas utilizando ArrayBlockingQueue (classe BufferBloqueante).
• A classe ArrayBlockingQueue pertence ao pacote java.util.concurrent, segura para threads e que implementa a interface BlockingQueue (que estende a classe Queue) e declara os métodos put e take (equivalentes bloqueantes dos métodos offer e poll.
• put coloca um elemento no fim da fila bloqueando se a mesma estiver cheia.
• take retira um elemento do começo da fila bloqueando se a mesma estiver vazia.
• O tamanho do ArrayBlockingQueue é passado como argumento para o construtor e não é aumentado dinamicamente.
ABORDAGEM 3 • Utilizar um buffer compartilhado com apenas uma
posição, mas utilizando métodos sincronizados (synchronized).
• Utiliza uma variável booleana ocupado que indica se o buffer está totalmente ocupado ou não.
• O produtor espera enquanto o buffer estiver lotado utilizando uma chamada ao método wait. Similarmente, o consumidor espera enquanto o buffer estiver vazio.
• O método wait faz com que o objeto que o executou libere implicitamente o bloqueio sobre o buffer.
• O produtor/consumidor só é liberado de sua espera quando for notificado de que o objeto compartilhado já foi utilizado; isso é feito com a chamada a notifyAll.
• O método notifyAll retira do estado de espera todas as threads que estiverem esperando pelo objeto compartilhado, fazendo com que elas passem ao estado executável e tentem readquirir o bloqueio.
ABORDAGEM 4 • Utilizar um buffer circular compartilhado com 4
posições.
• Permite minimizar o tempo de espera das threads devido ao aumento das posições disponíveis para escrita e leitura.
• Utiliza:
• uma variável auxiliar para controlar quantos elementos válidos realmente há no buffer em um determinado momento.
• Dois índices para indicar onde será feita a próxima leitura ou a próxima escrita.
MULTITHREADING COM GUI • Aplicativos GUI são desafiantes para programação
multithreaded.
• Aplicativos swing possuem uma única thread, a thread de despacho de eventos, responsável por tratar as interações com os componentes GUI do aplicativo.
• Todas as tarefas que exigem interação com a GUI do aplicativo são colocadas em uma fila de eventos e executadas em sequência pela thread de despacho de eventos.
• Componentes GUI Swing não são seguros para threads.
• Segurança para threads em aplicativos GUI é obtida assegurando que os componentes Swing são acessado a partir de uma única thread (a de despacho de eventos). Essa técnica é chamada de confinamento de thread.
• Utilizando essa ideia, uma alternativa interessante é colocar as atividades de longa duração, que independem da interface enquanto são executadas, em threads separadas da de despacho de eventos.
• Essa alternativa evita que a thread de despacho de eventos fique sem responder enquanto espera pelo resultado de tais atividades.
• Para auxiliar na programação dessas atividades mais demoradas em threads separadas, o Java SE 6 fornece a classe SwingWorker. Essa classe pode realizar cálculos demorados e, então, atualizar s componentes Swing a partir da thread de despacho de eventos com base nos resultados dos cálculos.
• SwingWorker implemente a interface Runnable.
• Métodos comuns de SwingWorker:
• doInBackground: define o cálculo longo na thread trabalhadora;
• done: executado na thread de despacho de eventos quando doInBackground retorna;
• execute: agenda o objeto SwingWorker a ser executado em uma thread trabalhadora;
• get: espera a conclusão do cálculo e retorna o seu resultado;
• publish: envia resultados intermediários dos cálculos para processamento na thread de despacho de eventos.
• process: recebe os resultados intermediários e os processa na thread de despacho de eventos;
• setProgress: configura a propriedade de progresso para notificar os ouvintes de alteração de propriedade na thread de despacho de eventos de atualizações da barra de progresso.
EXEMPLO 1: FIBONACCI • No exemplo, forneceremos uma opção para calcular os
termos da sequência de Fibonacci um a um ou para calcular um termo específico (até o 92º) em uma thread trabalhadora.
• SwingWorker é uma classe genérica que recebe dois parâmetros: o primeiro é o tipo retornado pelo método doInBackground e o segundo é o tipo passado entre os métodos publish e process.
EXEMPLO 2: ÍMPARES • No exemplo, calculamos todos os números ímpares
menores que um valor inteiro fornecido como entrada.
• Para obtenção dos ímpares, utilizamos o crivo de Eratóstenes.
• Os resultados são exibidos em uma área de texto à medida em que são obtidos.
• Uma barra de progresso mostra o quanto do cálculo já foi concluído até um determinado momento.
REFERÊNCIAS
• Java Como Programar – 8ª Edição. Deitel & Deitel.