No que se refere às construções machadianas, constatamos que a quebra do paralelismo semântico foi manifestada de forma intencional. Tal propósito se deve aos recursos utilizados pela linguagem literária, no intuito de conferir mais ênfase à mensagem. Dessa forma, temos que no segundo exemplo, o autor conseguiu obter um significativo efeito de estilo, bem como no primeiro, no qual se depreende que o recurso irônico, representando uma de suas marcas, foi utilizado para enfatizar o interesse financeiro de Marcela. Show
Partindo de tais pressupostos, cumpre dizer que mesmo em se tratando de um “desvio”, no caso da linguagem artística esse não é considerado como tal, em virtude do que chamamos de licença poética. Quanto ao terceiro exemplo, infere-se que a quebra também se deu no âmbito semântico – uma vez detectada pela quebra de expectativa por parte do leitor ao fazer a junção entre dois elementos de naturezas distintas: livros e frutas. Voltar a questão Artigos Java Threads: paralelizando tarefas com os diferentes recursos do Java Por que eu devo ler este artigo:Este artigo aborda o paralelismo de tarefas em softwares Java, pr�tica de desenvolvimento muito utilizada e necess�ria nos dias de hoje, onde temos arquiteturas computacionais essencialmente paralelas. Os desenvolvedores que desejam otimizar o desempenho de um software encontrar�o neste artigo explica��es sobre o funcionamento das principais APIs multithreaded da plataforma Java, bem como os problemas mais triviais e formas de como evit�-los. A plataforma Java disponibiliza diversas APIs para implementar o paralelismo desde as suas primeiras vers�es, e estas veem evoluindo at� hoje, trazendo novos recursos e frameworks de alto n�vel que auxiliam na programa��o. No entanto, deve-se lembrar que a tecnologia n�o � tudo. � importante, tamb�m, conhecer os conceitos desse tipo de programa��o e boas pr�ticas no desenvolvimento voltado para esse cen�rio. O processamento paralelo, ou concorrente, tem como base um hardware multicore, onde disp�e-se de v�rios n�cleos de processamento. Estas arquiteturas, no in�cio do Java, n�o eram t�o comuns. No entanto, atualmente j� se encontram amplamente difundidas, tanto no contexto comercial como dom�stico. Diante disso, para que n�o haja desperd�cio desses recursos de hardware e possamos extrair mais desempenho do software desenvolvido, � recomendado que alguma t�cnica de paralelismo seja utilizada. Como sabemos, existem diversas formas de criar uma aplica��o que implemente paralelismo, formas estas que se diferem tanto em t�cnicas como em tecnologias empregadas. Em vista disso, no decorrer deste artigo ser�o contextualizadas as principais APIs Java, desde as threads �cl�ssicas� a modernos frameworks de alto n�vel, visando otimizar a constru��o, a qualidade e o desempenho do software. Arquitetura MulticoreUma arquitetura multicore consiste em uma CPU que possui mais de um n�cleo de processamento. Este tipo de hardware permite a execu��o de mais de uma tarefa simultaneamente, ao contr�rio das CPUs singlecore, que eram constitu�das por apenas um n�cleo, o que significa, na pr�tica, que nada era executado efetivamente em paralelo. A partir do momento em que se tornou invi�vel desenvolver CPUs com frequ�ncias (GHz) mais altas, devido ao superaquecimento, partiu-se para outra abordagem: criar CPUs multicore, isto �, inserir v�rios n�cleos no mesmo chip, com a premissa base de dividir para conquistar. Ao contr�rio do que muitos pensam, no entanto, os processadores multicore n�o somam a capacidade de processamento, e sim possibilitam a divis�o das tarefas entre si. Deste modo, um processador de dois n�cleos com clock de 2.0 GHz n�o equivale a um processador com um n�cleo de 4.0 GHz. A tecnologia multicore simplesmente permite a divis�o de tarefas entre os n�cleos de tal forma que efetivamente se tenha um processamento paralelo e, com isso, seja alcan�ado o t�o almejado ganho de performance. Contudo, este ganho � poss�vel apenas se o software implementar paralelismo. Neste contexto, os Sistemas Operacionais, h� anos, j� possuem suporte a multicore, mas isso somente otimiza o desempenho do pr�prio SO, o que n�o � suficiente. O ideal � cada software desenvolvido esteja apto a usufruir de todos os recursos de hardware dispon�veis para ele. Ademais, considerando o fato de que hoje j� nos deparamos com celulares com processadores de quatro ou oito n�cleos, os softwares a eles disponibilizados devem estar preparados para lidar com esta arquitetura. Desde um simples projeto de rob�tica a um software massivamente paralelo para um supercomputador de milh�es de n�cleos, a op��o por paralelizar ou n�o, pode significar a diferen�a entre passar dias processando uma determinada tarefa ou apenas alguns minutos. MultitaskingO multitasking, ou multitarefa, � a capacidade que sistemas possuem de executar v�rias tarefas ou processos ao mesmo tempo, compartilhando recursos de processamento como a CPU. Esta habilidade permite ao sistema operacional intercalar rapidamente os processos ativos para ocuparem a CPU, dando a impress�o de que est�o sendo executados simultaneamente, conforme a Figura 1. No caso de uma arquitetura singlecore, � poss�vel executar apenas uma tarefa por vez. Mas com o multitasking esse problema � contornado gerenciando as tarefas a serem executadas atrav�s de uma fila, onde cada uma executa por um determinado tempo na CPU. Nos sistemas operacionais isto se chama escalonamento de processos. Figura 1. Processos executando em um n�cleo. Em arquiteturas multicore, efetivamente os processos podem ser executados simultaneamente, conforme a Figura 2, mas ainda depende do escalonamento no sistema operacional, pois geralmente temos mais processos ativos do que n�cleos dispon�veis para processar. Figura 2. Arquitetura multicore executando processos. Desta forma, mais n�cleos de processamento significam que mais tarefas simult�neas podem ser desempenhadas. Contudo, vale ressaltar que isto s� � poss�vel se o software que est� sendo executado sobre tal arquitetura implementa o processamento concorrente. De nada adianta um processador de oito n�cleos se o software utiliza apenas um. MultithreadingDe certo modo, podemos compreender multithreading como uma evolu��o do multitasking, mas em n�vel de processo. Ele, basicamente, permite ao software subdividir suas tarefas em trechos de c�digo independentes e capazes de executar em paralelo, chamados de threads. Com isto, cada uma destas tarefas pode ser executada em paralelo caso haja v�rios n�cleos, conforme demonstra a Figura 3. Figura 3. Processo executando v�rias tarefas. Diversos benef�cios s�o adquiridos com este recurso, mas, sem d�vida, o mais procurado � o ganho de performance. Al�m deste, no entanto, tamb�m � v�lido destacar o uso mais eficiente da CPU. Sabendo dessa import�ncia, nosso pr�ximo passo � entender o que s�o as threads e como cri�-las para subdividir as tarefas do software. ThreadsNa plataforma Java, as threads s�o, de fato, o �nico mecanismo de concorr�ncia suportado. De forma simples, podemos entender esse recurso como trechos de c�digo que operam independentemente da sequ�ncia de execu��o principal. Como diferencial, enquanto os processos de software n�o dividem um mesmo espa�o de mem�ria, as threads, sim, e isso lhes permite compartilhar dados e informa��es dentro do contexto do software. Cada objeto de thread possui um identificador �nico e inalter�vel, um nome, uma prioridade, um estado, um gerenciador de exce��es, um espa�o para armazenamento local e uma s�rie de estruturas utilizadas pela JVM e pelo sistema operacional, salvando seu contexto enquanto ela permanece pausada pelo escalonador. Na JVM, as threads s�o escalonadas de forma preemptiva seguindo a metodologia �round-robin�. Isso quer dizer que o escalonador pode paus�-las e dar espa�o e tempo para outra thread ser executada, conforme a Figura 4. O tempo que cada thread recebe para processar se d� conforme a prioridade que ela possui, ou seja, threads com prioridade mais alta ganham mais tempo para processar e s�o escalonadas com mais frequ�ncia do que as outras. Figura 4. Escalonamento de threads, modo round-robin. Tamb�m � poss�vel observar na Figura 4 que apenas uma thread � executada por vez. Isto normalmente acontece em casos onde s� h� um n�cleo de processamento, o software implementa um sincronismo de threads que n�o as permite executar em paralelo ou quando o sistema n�o faz uso de threads. Na Figura 5, por outro lado, temos um cen�rio bem diferente, com v�rias threads executando paralelamente e otimizando o uso da CPU. Figura 5. Escalonamento de threads no modo round-robin implementando paralelismo. Desde seu in�cio a plataforma Java foi projetada para suportar programa��o concorrente. De l� para c�, principalmente a partir da vers�o 5, foram inclu�das APIs de alto n�vel que nos fornecem cada vez mais recursos para a implementa��o de tarefas paralelas, como as APIs presentes nos pacotes java.util.concurrent.*. Saiba que toda aplica��o Java possui, no m�nimo, uma thread. Esta � criada e iniciada pela JVM quando iniciamos a aplica��o e sua tarefa � executar o m�todo main() da classe principal. Ela, portanto, executar� sequencialmente os c�digos contidos neste m�todo at� que termine, quando a thread encerrar� seu processamento e a aplica��o poder� ser finalizada. Em Java, existem basicamente duas maneiras de criar threads:
Na Listagem 1, de forma simples e objetiva, � apresentado um exemplo de como implementar uma Thread para executar uma subtarefa em paralelo. Para isso, primeiramente � necess�rio codificar um Runnable, o que pode ser feito diretamente na cria��o da Thread, como demonstrado na Listagem 1, ou implementar uma classe pr�pria que estenda Runnable. Posteriormente, basta execut�-lo com um objeto Thread atrav�s do m�todo start(). Listagem 1. Exemplo de thread implementando a interface Runnable.
Neste exemplo pode-se observar tamb�m o c�digo utilizado para buscar alguns dados da thread atual, tais como ID, nome, prioridade, estado e at� mesmo capturar o c�digo que ela est� executando. Al�m de tais informa��es que podem ser capturadas, � poss�vel manipular as threads utilizando alguns dos seguintes m�todos:
A forma cl�ssica de se criar uma thread � estendendo a classe Thread, como demonstrado na Listagem 2. Neste c�digo, temos a classe Tarefa estendendo a Thread. A partir disso, basta sobrescrever o m�todo run(), o qual fica encarregado de executar o c�digo da thread. Na pr�tica, nossa classe Tarefa � respons�vel por realizar o somat�rio do intervalo de valores recebido no momento em que ela � criada e armazen�-lo em uma vari�vel para que possa ser lido posteriormente. Listagem 2. C�digo da classe Tarefa estendendo a classe Thread.
Listagem 3. C�digo da classe Exemplo, utiliza a classe Tarefa.
Para testarmos o paralelismo com a classe da Listagem 2, criamos a classe Exemplo com o m�todo main(), respons�vel por executar o programa (vide Listagem 3). Neste exemplo, ap�s criar as threads, chama-se o m�todo start() de cada uma delas, para que iniciem suas tarefas. Logo ap�s, em um bloco try-catch, temos a invoca��o dos m�todos join(). Este faz com que o programa aguarde a finaliza��o de cada thread para que depois possa ler o valor totalizado por cada tarefa. Observe, na Listagem 3, que cada tarefa recebe seu intervalo de valores a calcular, sendo somado, ao todo, de 0 a 3000, mas e se tiv�ssemos uma �nica lista de valores que gostar�amos de somar para obter o valor total? Neste caso, as threads precisariam concorrer pela lista. Isso � o que chamamos de concorr�ncia de dados e geralmente traz consigo diversos problemas. Concorr�ncia de dadosA concorr�ncia de dados � um dos principais problemas a se enfrentar quando empregamos multithreading em uma aplica��o. Ela � capaz de gerar desde inconsist�ncia nos dados compartilhados at� erros em tempo de execu��o. No entanto, felizmente isto pode ser evitado, sendo necess�rio, portanto, se precaver para que nosso aplicativo n�o apresente tais problemas. Uma boa forma de evitar problemas de concorr�ncia � sincronizar as threads que compartilham dados entre si. A partir disso, estas threads passam a executar em sincronia com outras, e assim, uma por vez acessar� o recurso. O sincronismo previne que duas ou mais threads acessem o mesmo recurso simultaneamente. Por outro lado, temos as threads ass�ncronas, que executam independentemente umas das outras e geralmente n�o compartilham recursos, como � o caso do exemplo das Listagens 2 e 3. No exemplo da Listagem 4, por sua vez, � poss�vel visualizar tr�s threads disputando a mesma vari�vel varCompartilhada para increment�-la de forma ass�ncrona. Basicamente, a ideia desse c�digo � incrementar uma vari�vel com diferentes valores e, a cada valor gerado, adicion�-lo em uma lista (ArrayList). Listagem 4. Exemplo de concorr�ncia utilizando lista ass�ncrona.
No entanto, ao executar este algoritmo � prov�vel que seja gerada a exce��o java.lang.ArrayIndexOutOfBoundsException, devido � concorr�ncia pela lista, visto que h� mais de uma thread tentando inserir dados nela. Como o �ponto fraco� desta estrutura de dados � seu mecanismo din�mico de tamanho vari�vel, a cada novo valor a ser inserido � preciso expandir a lista. Desta forma, a thread perde tempo para fazer esta opera��o, aumentando assim a possibilidade de ser pausada pelo escalonador. Quando isto acontece e alguma outra thread tenta realizar a mesma opera��o de add(), a exce��o � gerada. Com o intuito de solucionar esse problema, uma das op��es � adotar uma lista sincronizada, conforme o c�digo a seguir:
Apesar de solucionar o problema anterior, ainda � poss�vel que a thread sofra interrup��o durante o incremento da vari�vel varCompartilhada e passe a gerar valores inconsistentes. Isto porque no processo atual de incremento da vari�vel, primeiramente deve ser pego o valor atual desta, som�-lo com 1 e ent�o obter o novo valor a ser armazenado. Esse problema acontece porque nesse c�digo existem tr�s threads alterando o valor da mesma vari�vel (nesse caso, com o operador ++) e o escalonador, quando aloca uma thread ao processador, permite que ela execute seu c�digo por um determinado per�odo de tempo e depois a interrompe, possibilitando que outra thread ocupe seu lugar e opere sobre os mesmos dados. Assim, quando a thread anterior voltar a processar, trabalhar� com valores desatualizados. Para aferir o resultado deste algoritmo, toda atualiza��o de valor da vari�vel varCompartilhada � adicionada a uma lista e ao final � realizada a soma de todos esses valores. Por causa das situa��es supracitadas, no entanto, o resultado gerado a cada execu��o pode ser diferente. Isto demonstra que o incremento de uma vari�vel ass�ncrona em threads �, sem d�vidas, um problema. Nota: � preciso destacar que nem sempre ocorrer� esse problema, ou seja, nem sempre uma thread ser� interrompida durante o seu processamento. Para aumentar as chances desse problema acontecer, foi utilizado um intervalo de 10.000 repeti��es e tr�s threads. Com um n�mero baixo de itera��es, coincidentemente pode ser gerado o mesmo resultado em quase todas as execu��es. O exemplo apresentado na Listagem 5 traz uma deriva��o do c�digo da Listagem 4. Neste caso, o List foi substitu�do por um Set, que suporta a inser��o de valores de modo ass�ncrono e ainda garante a unicidade dos valores inseridos. Assim, n�o mais teremos problemas com o ArrayList e poderemos dar sequ�ncia � demonstra��o do problema de concorr�ncia com a varCompartilhada. Listagem 5. Exemplo de concorr�ncia utilizando HashSet.
Ao executar este algoritmo diversas vezes � poss�vel observar (vide Figura 6) que ele imprime no console alguns valores a serem inseridos que j� existem no Set, o que demonstra que as threads est�o incrementando a vari�vel, mas em algum momento geram o mesmo valor. Isso acontece por causa da concorr�ncia pela vari�vel varCompartilhada de maneira ass�ncrona, onde ao incrementar esta vari�vel, mais de uma thread acaba gerando o mesmo valor. Figura 6. Resultado no console com a execu��o da Listagem 5. Sincroniza��o de ThreadsCaso n�o seja uma op��o substituir o ArrayList, uma alternativa para solucionar o problema obtido na Listagem 4 � sincronizar o objeto concorrido; neste caso, a lista (vide Listagem 6). Isso � poss�vel porque todo objeto Java possui um lock associado, que pode ser disputado por qualquer trecho de c�digo sincronizado e em qualquer thread. Listagem 6. Exemplo de sincroniza��o de vari�vel com bloco de c�digo sincronizado.
Um bloco sincronizado previne que mais de uma thread consiga execut�-lo simultaneamente. Para isso, a thread que for utilizar esse bloco adquire o lock associado ao objeto sincronizado e as demais que tentarem acess�-lo entrar�o em estado de BLOCKED, at� que o objeto seja liberado. Na Figura 7 � poss�vel observar o ciclo de vida de uma thread, da sua cria��o � sua finaliza��o. A seguir s�o descritos os poss�veis estados que elas podem assumir:
Figura 7. Ciclo de vida de uma thread. Nota: Ao sincronizar opera��es, prefira sempre o uso de m�todos sincronizados no lugar de blocos desse tipo. Isso porque os bytecodes gerados para um m�todo sincronizado s�o relativamente menores do que os gerados para um bloco sincronizado. Outra forma de acessar um dado compartilhado entre threads � criando um m�todo sincronizado. Essa t�cnica � muito parecida com a anterior, mas ao inv�s de sincronizar o mesmo bloco de c�digo em cada thread, ele � transferido para um m�todo que cont�m a nota��o synchronized na assinatura. Assim, as threads ter�o que invoc�-lo para realizar a opera��o sobre o dado concorrente. Veja um exemplo na Listagem 7. Listagem 7. Exemplo de m�todo sincronizado.
Nota: O ato de adquirir bloqueios para sincronizar threads consome tempo, mesmo quando nenhuma precisa aguardar a libera��o do objeto sincronizado. Esse processo � uma faca de dois gumes: se por um lado ele resolve problemas de concorr�ncia, por outro serializa o processamento das threads sobre esse bloco; ou seja, as threads nunca estar�o processando esse c�digo simultaneamente, o que pode degradar o desempenho. Portanto, esse recurso deve ser usado com modera��o e somente onde for necess�rio. Vari�veis at�micasQuando � preciso utilizar tipos primitivos de forma concorrente uma boa op��o � adotar seu respectivo tipo at�mico, presente no pacote java.util.concurrent.atomic. Este tipo de objeto disponibiliza opera��es como incremento atrav�s de m�todos pr�prios e s�o executadas em baixo n�vel de hardware, de forma que a thread n�o ser� interrompida durante o processo. Deste modo n�o � necess�rio sincronizar o objeto, gerando um algoritmo sem bloqueios e muito mais r�pido. Veja o c�digo a seguir:
Neste caso, ao inv�s de utilizar um Integer para armazenar o valor, foi instanciado um AtomicInteger. Com isso, pode-se trocar o varCompartilhada++ pela chamada varCompartilhada.incrementAndGet(), que realizar� uma fun��o semelhante de forma at�mica, o que garantir� que a thread n�o seja interrompida no meio do processo de incremento da vari�vel. Nota: Em tipos at�micos, m�todos que n�o modificam seu valor s�o sincronizados. Interface CallableA interface Runnable � utilizada desde as primeiras vers�es da plataforma Java e como todos j� sabem, ela fornece um �nico m�todo � run() � que n�o aceita par�metros e n�o retorna valor, assim como n�o pode lan�ar qualquer tipo de exce��o. No entanto, e se precis�ssemos executar uma tarefa em paralelo e ao final obter um resultado como retorno? Para solucionar esse problema, voc� poderia criar um m�todo na classe que implementa Thread ou Runnable e esperar pela conclus�o da tarefa para acessar o resultado, assim como no cen�rio da Listagem 8. Listagem 8. Exemplo de leitura de resultado em tarefa com Thread.
Basicamente n�o h� nada de errado com esse c�digo, mas a partir do Java 5 este processo pode ser feito de forma diferente, gra�as � interface Callable. Deste modo, em vez de ter um m�todo run(), a interface Callable oferece um m�todo call(), que pode retornar um objeto qualquer, al�m da grande vantagem de poder capturar uma exce��o gerada pela tarefa da thread. Para tirar proveito dos benef�cios de um objeto Callable, � altamente recomend�vel n�o utilizar um objeto Thread para execut�-lo, e sim alguma outra API, como:
As implementa��es apresentadas nas Listagens 9 e 10 demonstram uma boa pr�tica no uso de Callables. Este c�digo cria tr�s tarefas que levam um determinado tempo para concluir e, ao terminar, retornam o nome da thread que a realizou. O c�digo da tarefa se encontra na classe ExemploCallable, que implementa a interface Callable, com retorno do tipo String. Com esta interface a tarefa que se deseja executar deve ser implementada no m�todo call() (vide Listagem 9), o qual � invocado ao executar o objeto Callable. Listagem 9. Exemplo de classe implementando Callable.
Listagem 10. Exemplo de tarefas com retorno utilizando Callable.
O c�digo da Listagem 10 tem o objetivo de criar e executar tr�s tarefas armazenadas em uma lista. Para simular uma diferen�a no tempo de execu��o das threads, cada uma foi desenvolvida para aguardar um certo tempo em milissegundos, que lhe � fornecido no m�todo construtor. Antes de execut�-las, no entanto, note que � criado um pool de threads com um ExecutorService, o qual posteriormente � utilizado para criar um ExecutorCompletionService, que ser� encarregado de executar as tarefas e tamb�m nos ser� �til para receber o retorno de cada uma delas conforme forem concluindo. Dito isso, uma a uma as tarefas s�o executadas atrav�s do m�todo submit() e, por fim, � utilizado o m�todo take(), para buscar a tarefa conclu�da, e o m�todo get(), que l� o retorno dela e o imprime no console (Figura 8). Figura 8. Resultado da Listagem 10 no console. Nota: Note, pelo resultado da Figura 8, que, por mais que a tarefa que tinha dura��o de oito segundos seja executada primeiro, seu resultado aparece por �ltimo. Esse resultado � obtido porque a leitura dos retornos de cada tarefa n�o tem rela��o com a ordem de execu��o, e sim com sua conclus�o, gra�as ao ExecutorCompletionService. Cole��es concorrentes vs Cole��es sincronizadasUm recurso bastante utilizado no desenvolvimento de software s�o as cole��es de dados. Na plataforma Java estas estruturas est�o dispon�veis em uma s�rie de implementa��es para os mais diversos fins. Como sabemos, n�o h� nenhum �mist�rio� em declar�-las, no entanto, como � comum nos depararmos com bugs ao acessar essas estruturas de maneira concorrente, vale dedicar um t�pico deste artigo para explorar suas peculiaridades. Dentre as cole��es dispon�veis no Java, existem variados tipos de estruturas de dados, como, listas, pilhas e filas, e estas, por sua vez, ainda se subdividem quanto a forma de implementa��o, que compreende:
Sabendo disso, preferencialmente, opte por utilizar cole��es concorrentes, ao inv�s das sincronizadas, pois as cole��es concorrentes possuem maior escalabilidade e suportam modifica��es simult�neas de diversas threads sem precisar estabelecer um bloqueio. J� as cole��es sincronizadas t�m sua performance degradada devido ao bloqueio que precisam estabelecer quando uma thread as acessa. Logo, isso tamb�m significa que somente uma thread por vez pode modific�-las. Um detalhe que costuma passar despercebido nas entrelinhas da programa��o concorrente � que n�o existe a garantia de execu��o paralela ou de que cada thread vai executar em um n�cleo diferente. Criar threads apenas sugere � JVM que aquilo seja paralelizado. Por exemplo, voc� pode ter um processador de quatro n�cleos e criar um aplicativo com quatro threads que processem exaustivamente, mas isso n�o lhe garante que cada uma das quatro threads ser�o executadas por um n�cleo diferente, t�o pouco consumir�o 100% de processamento. Portanto, n�o basta criar threads pensando que isto � a solu��o dos seus problemas. Neste caso, ao criar threads em demasia estar-se-ia degradando a performance, j� que a JVM gastaria muito tempo com o escalonamento delas, se comparado ao tempo total de processamento utilizado pelas threads. Primeiramente, a aplica��o deve ser inteligente o bastante para criar o n�mero ideal de threads, ou seja, deve ser levada em considera��o a quantidade de processadores/n�cleos dispon�veis no sistema. Criar um n�mero de threads menor do que o n�mero de n�cleos dispon�veis gera desperd�cio. Por outro lado, gerar um n�mero excessivamente maior de threads, causar� outro problema. Ser� perdido mais tempo com o escalonamento das threads do que com as pr�prias tarefas que elas precisam executar, e assim, por mais que se esteja consumindo 100% da CPU, n�o se tem o desempenho m�ximo que se pode atingir. Para amenizar este problema, um recurso muito �til da plataforma Java pode ser verificado no c�digo apresentado a seguir, que permite ler a quantidade de n�cleos dispon�veis. A partir disso, podemos calcular o n�mero ideal de threads necess�rias para atingir os 100% de processamento sem desperd�cios, quando temos uma aplica��o que precisa realizar um c�lculo exaustivo:
FrameworkFork/JoinO frameworkFork/Join, introduzido na vers�o 7 da plataforma Java, � uma implementa��o da interface ExecutorService que auxilia o desenvolvedor a tirar proveito das arquiteturas multicore. Esta API foi projetada para as tarefas que podem ser quebradas em pequenas partes recursivamente, com o objetivo de usar todo o poder de processamento dispon�vel para melhorar o desempenho da aplica��o. O exemplo apresentado nas Listagens 11 e 12 demonstra um cen�rio onde o objetivo � buscar, recursivamente em um sistema de arquivos, os arquivos com determinada extens�o. Ao iniciar, a tarefa recebe um diret�rio base onde o algoritmo come�a as buscas. O conte�do do diret�rio � ent�o analisado e caso haja outra pasta dentro desta, � criada outra tarefa para analisar aquele diret�rio, e assim recursivamente o algoritmo realiza a busca pelos arquivos e retorna os resultados � tarefa pai. Tecnicamente, para realizar este processo foi implementada uma classe que estende RecursiveTask e recebe um List de String, o qual � utilizado para informar o tipo de retorno da tarefa (vide Listagem 11). Ao criar a tarefa, ou seja, uma inst�ncia da classe ProcessadorDePastas, � necess�rio informar por par�metros o diret�rio base onde se iniciar� a busca e a extens�o de arquivo pela qual se dar� a busca. Quando se estende a classe RecursiveTask, deve ser implementado o m�todo compute(), que � respons�vel por desempenhar a tarefa desejada, assim como devemos codificar o m�todo run(), quando se implementa a interface Runnable. � neste m�todo que est� especificada a busca pelos arquivos. Nele, o ponto mais importante pode ser verificado na recursividade, local que cria as tarefas paralelas com a chamada ao m�todo fork() para cada pasta localizada dentro da pasta na qual se est� pesquisando. Ao final, cada subtarefa retorna os dados de sua busca � tarefa que a criou, e esta, por sua vez, adiciona estes dados na lista �tarefas�. Este � o processo de desempilhar a recurs�o, que � realizado at� chegar � primeira tarefa criada na classe ForkJoinMain, momento este em que os dados s�o retornados para a lista resultados pelo m�todo join() (vide Listagem 12). Listagem 11. Exemplo de tarefa Fork/Join.
Na Listagem 12 temos o c�digo respons�vel por iniciar a tarefa principal, ler e exibir os resultados. Para tal, foram criadas tr�s tarefas base que far�o as buscas em tr�s pastas distintas, e a fim de execut�-las, foi instanciado um pool de threads com um ForkJoinPool. Este tipo de pool gerencia de forma mais eficiente o trabalho das threads, pois utiliza uma t�cnica chamada de �roubo de tarefa� para executar as tarefas em espera. Nesta abordagem cada thread possui uma fila de tarefas em espera e no momento em que uma thread n�o tiver mais nada em sua fila, poder� �roubar� o trabalho de outra, possibilitando mais uma melhoria na performance. Listagem 12. Exemplo de uso da tarefa Fork/Join.
Por fim, saiba que enquanto o aplicativo processa � poss�vel extrair algumas informa��es �teis, a fim de monitorar o trabalho do framework e do pool de threads. Estes dados podem ser obtidos com o pr�prio objeto do pool, atrav�s dos seguintes m�todos:
Java 8 � Lambdas e StreamsA vers�o 8 da plataforma Java tem como uma das suas principais caracter�sticas o suporte a express�es Lambda, recurso que foi projetado com o intuito de facilitar a programa��o funcional e reduzir o tempo de desenvolvimento. Isso pode ser exemplificado criando um objeto Thread, como exp�e o c�digo da Listagem 13, onde � poss�vel notar que a cria��o do objeto Runnable se torna impl�cita, reduzindo de cinco para duas a quantidade de linhas necess�rias para a cria��o de uma thread. Listagem 13. Criando uma thread com express�es lambda.
Ainda no Java 8, uma nova abstra��o, chamada Stream, foi desenvolvida. Esta permite processar dados de forma declarativa, assim como possibilita a execu��o de tarefas utilizando v�rios n�cleos sem que seja necess�rio implementar uma linha de c�digo multithreading, atrav�s da fun��o parallelStream. Quando um stream � executado em paralelo, a JVM o particiona em v�rios substreams, os quais s�o iterados individualmente por threads e, por fim, seus resultados s�o combinados (veja Listagem 14). Al�m de ser extremamente simples e funcional, em poucas linhas � poss�vel extrair v�rias informa��es de uma lista num�rica, como valor m�ximo, m�nimo, soma total e m�dia, sem ter que se preocupar em desenvolver estas fun��es. E mesmo que sua lista n�o seja num�rica, ainda assim se tornou mais f�cil transformar ou extrair informa��es por meio das express�es lambda. Listagem 14. Exemplo utilizando ParallelStream.
O Java foi uma das primeiras plataformas a fornecer suporte a multithreading no n�vel de linguagem e agora � uma das primeiras a padronizar utilit�rios e APIs de alto n�vel para lidar com threads, como a introdu��o do framework Fork/Join na vers�o 7, e a API de streams e o suporte a express�es lambda na vers�o 8. Atualmente, qualquer computador ou smartphone tem mais de um n�cleo de processamento e a cada novo lan�amento esta quantidade s� aumenta, assim como a import�ncia do software ser desenvolvido em multithreading. Atendendo a esse cen�rio, o Java fornece uma base s�lida para a cria��o de uma ampla variedade de solu��es paralelas. Para finalizar, note que � poss�vel alcan�ar bons resultados com as t�cnicas aqui demonstradas. Contudo, sempre utilize a programa��o concorrente com bastante aten��o, pois ao manipular dados compartilhados entre threads poder� cair em alguns cen�rios de depura��o bem dif�ceis.
Confira outros conte�dos:Plano PRO
Por Rodrigo Em 2016 Receba nossas novidadesO QUE É paralelismo de tarefas?Paralelismo de tarefa é a forma mais simples de programação paralela, onde as aplicações estão divididas em tarefas exclusivas que são independentes umas das outras e podem ser executadas em processadores diferentes.
Quais as vantagens do paralelismo?Em grandes empresas e indústrias, o paralelismo contribui para evitar prejuízos que podem ocorrer durante a manutenção do gerador, já que com mais de um equipamento em funcionamento, não há problemas com interrupção do fornecimento de energia, e automaticamente, da produção.
São as operações usadas em CUDA exceto?Essa API será a: MPI (Message Passing Interface) Cilk++ POSIX CUDA (Compute Unified Device Architecture) OpenMP Respondido em 23/09/2021 16:57:16 Explicação: A resposta certa é: MPI (Message Passing Interface) 9a Questão Acerto: 1,0 / 1,0 São as operações usadas em CUDA, exceto: Transferência de dados.
Qual é a maior vantagem de usar paralelismo?A maior vantagem do paralelismo OU é o fato de que ele vem de forma natural e sem muito overhead.
|