O documento discute multithreading em Java, abordando tópicos como threads, seu ciclo de vida, agendamento, criação e execução. Apresenta também exemplos de produtor-consumidor e uso de threads com GUI, mostrando como resolver problemas de concorrência de forma segura.
Polígonos, Diagonais de um Polígono, SOMA DOS ANGULOS INTERNOS DE UM POLÍGON...
Aula sobre multithreading
1. Linguagem de Programação III
Multithreading
Professora: Bianca de Almeida Dantas
E-mail: bianca.dantas@ifms.edu.br
Site: www.biancadantas.com
2. TÓPICOS
• Threads Essa aula e seus exemplos foram baseados no
capítulo 26 do livro Java - Como Programar, 8ª
• Ciclo de vida Edição (Deitel & Deitel)
• Agendamento
• Criação e execução
• Exemplo
• Produtor-Consumidor
• Multithreading com GUI
3. 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.
4. • 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.
5. • 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.
7. • 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.
8. • 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).
9. • 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.
10. • 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.
11.
12. THREADS – Agendamento
• 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.
14. THREADS – Criação e Execução
• 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.
16. 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]
17. • 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.
18. • 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.
19. 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.
20. 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.
21. 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.
22. 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.
23. • 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.
24. 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.
25. • 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.
26. 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.
27. 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.
28. • 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.
29. • 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.
30. • 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.
31. • 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.
32. 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.
33. 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.