Multithreading
Tese: Multithreading. Pesquise 862.000+ trabalhos acadêmicosPor: Leandrosp • 4/9/2013 • Tese • 1.938 Palavras (8 Páginas) • 463 Visualizações
1 MULTITHREADING
Tipicamente, os sistemas operacionais oferecem suporte a processos para o desenvolvimento de aplicações concorrentes. A utilização comercial de sistemas operacionais e aplicações multithread é recente, mas sua implementação está crescendo devido ao aumento de popularidade dos sistemas com múltiplos processadores, do modelo cliente-servidor e dos sistemas distribuídos.
Com threads (linha ou encadeamento de execução) um processo pode ter diferentes partes do seu código sendo executadas concorrentemente ou simultaneamente, com muito menos overhead (processamento ou armazenamento em excesso) que utilizando múltiplos (sub)processos. Como os threads de um mesmo processo compartilham o mesmo espaço de endereço, a comunicação dos threads não envolve mecanismos lentos de intercomunicação entre processos.
Basicamente Multithreading é a técnica de programação concorrente, que permite projetar e implementar aplicações paralelas de forma eficiente. O desenvolvimento de programas que exploram os benefícios da programação multithread não é simples. A presença do paralelismo introduz um novo conjunto de problemas, como a comunicação e sincronização de threads.
Uma thread inicia seu ciclo de vida no estado de novo, e permanece nesse estado até o programa iniciar a thread, o que a coloca no estado executável(executando sua tarefa). Pode ocorrer de uma thread entrar no estado de espera, que é quando outra thread realiza uma tarefa, e ela só volta a ser executada quando outra thread sinalizar para ela retornar a execução. Outro estado da thread é o de espera sincronizada que ocorre quando uma thread executável entra no estado de espera por um tempo especificado e retorna ao estado de executável quando esse tempo expira ou quando ocorre o evento que ele está esperando.
Um thread executável entra no estado de terminado quando completa sua tarefa. Na maioria dos sistemas operacionais, cada thread recebe uma pequena quantidade de tempo de processador, denominadaquantum ou fração de tempo, com o qual realiza sua tarefa. Quando esse quantum expirar a thread retornará ao estado de pronto e o SO atribuirá outra thread ao processador.
Cada thread Java tem uma prioridade que ajuda o sistema operacional a determinar a ordem em que as threads são agendadas. As prioridades do Java estão entre MIN_PRIORITY e MAX_PRIORITY,sendo esta última a thread com prioridade mais alta e deve ser alocada em termos de processador antes das threads de prioridade mais baixa.
Entretanto, as prioridades de thread não podem garantir a ordem em que elas são executadas. Por padrão, cada thread recebe a prioridade NORM_PRIORITY e cada thread nova herda a prioridade da thread que a criou. A maioria das plataformas do Java suporta o fracionamento de tempo, o que permite que threads de igual prioridade compartilhem um processador. Sem o fracionamento de tempo, cada thread em um conjunto de threads de igual prioridade executa até a sua conclusão, antes que outras de igual prioridade tenham a chance de executar. Já com o fracionamento de tempo, mesmo que a thread não tenha concluído a execução quanto o quantum expirar, o processador é tirado dessa thread e recebe a próxima de igual prioridade, se houver alguma disponível.
Quem determina àpróxima thread que irá entrar em execução é o scheduler de thread (agendador de thread), que mantém a thread de prioridade mais alta em execução o tempo todo e, se houver mais de uma thread de prioridade mais alta, isso assegura que cada uma delas executa por um quantum no estilo rodízio.
O modo preferido de criar um aplicativo de múltiplas threads é implementar a interface Runnablee utilizar classes e métodos predefinidos para criar threads que executam os objetos dessa interface. A interface Runnable declara um único método chamado run e são executados por um objeto de uma classe que implementa a interface Executor, que por sua vez declara um único método chamado execute. Em geral, um objeto Executor cria e gerencia um grupo de threads denominado pool de threads, que por sua vez executam os objetos Runnable passados para o método execute. O Executor atribui cada Runnable a uma das threads disponíveis no pool de threads e se não houver nenhuma thread disponível no pool de threads, ele cria uma nova thread ou espera que uma se torne disponível para atribuir a ela o Runnable que foi passado para método execute.
As threads de um mesmo processo compartilham o mesmo espaço de endereçamento de memória. Nesse caso, threads compartilham recursos comuns e para gerenciar o uso desses recursos precisam se sincronizar. Além disso, cada thread pode acessar qualquer endereço de memória dentro deste espaço. Dessa forma, uma thread pode alterar dados em outra, já que não existe mecanismo de proteção de acesso as threads devem cooperar entre si, o que mostra, mais uma vez a necessidade de sincronização.
Em um relacionamento produtor/consumidor, a parte produtora de um aplicativo gera dados e os armazenas em um objeto compartilhado, e a parte consumidora de um aplicativo lê os dados do objeto compartilhado, um exemplo disso é o spooling de impressão, onde um processador de texto faz um spool de dados para um buffer e esses dados são subsequentemente consumidos pela impressora à medida que ela imprime o documento.
Em um relacionamento produtor/consumidor de múltiplas threads, uma thread produtora gera dados e os coloca em um objeto compartilhado chamado buffer. Uma thread consumidora lê dados do buffer. Se a produtora que está esperando para colocar os próximos dados no buffer determinar que a consumidora ainda não leu os dados anteriores, a thread produtora deve chamar await (aguardar, esperar), de modo que o consumidor possa ler os dados antes das atualizações adicionais, caso contrário, a consumidora nunca vê os dados anteriores e estes são perdidos para o aplicativo.
Para minimizar a quantidade de tempo de espera por threads que compartilham recursos e operam nas mesmas velocidades médias, pode-se implementar um buffer circular, que fornece espaço de buffer extra em que a produtora pode colocar valores e a partir do qual a consumidora pode recuperá-las.
A interface BlockingQueue declara os métodos put e take, que são equivalentes de bloqueio de métodos Queueoffer e remove, respectivamente. Isso significa que o método put colocará um elemento no fim de BlockingQueue, esperando se a fila estiver cheia.
As interfaces Swing não são seguros para thread, pois se as múltiplas threads manipulam um componente GUI Swing os resultados podem não estar corretos.
...