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.
PROVA - ESTUDO CONTEMPORÂNEO E TRANSVERSAL: LEITURA DE IMAGENS, GRÁFICOS E MA...
Multithreading Java
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.