• Nenhum resultado encontrado

Programação Concorrente Segura em Java

N/A
N/A
Protected

Academic year: 2021

Share "Programação Concorrente Segura em Java"

Copied!
57
0
0

Texto

(1)

Programação Concorrente Segura em Java

Florianópolis Fevereiro de 2007

(2)

Programação Concorrente Segura em Java

Trabalho de conclusão de curso apresentado como parte dos requisitos para obtenção do grau de Bacharel em Ciências da Computação

Orientador:

José Mazzucco Júnior

BACHARELADO EMCIÊNCIAS DA COMPUTAÇÃO

DEPARTAMENTO DE INFORMÁTICA EESTATÍSTICA

CENTROTECNOLÓGICO

UNIVERSIDADEFEDERAL DE SANTA CATARINA

Florianópolis Fevereiro de 2007

(3)

Com o aumento da complexidade dos sistemas computacionais veio à necessidade da uti-lização softwares que possam agir de forma concorrente, utilizando-se o conceito de thread para isso. Porém, o sincronismo e o escalonamento das threads de Java são mal especificados, deixando a cargo do sistema operacional a especificação destas regras, gerando desempenhos diferentes ao utilizar sistemas operacionais distintos. A liberdade da programação concorrente em Java aumenta o perigo de hazards, deadlock, livelock e starvation, problemas comuns a aplicações multithread. Tais problemas podem ser evitados com a utilização de CTJ, uma bib-lioteca que implementa o conceito de Communicating Seqüencial Process (CSP), uma teoria matemática para descrição de computação concorrente e distribuída, que garante a ausência dos problemas citados acima através de regras algébricas. Este trabalho tem como objetivo com-parar aplicações desenvolvidas em Java e aplicações desenvolvidas utilizando-se a biblioteca CTJ.

(4)

1 Canal estendendo um linkdriver. . . p. 24 2 Exemplo de uma classe que implementa a interface de Processo. . . p. 25 3 Criando e executando um processo. . . p. 26 4 Construtor de composição seqüencial. . . p. 26 5 Exemplo de uma composição seqüencial. . . p. 27 6 Adição de um novo processo à composição seqüencial. . . p. 27 7 Adição de vários novos processos a composição seqüencial. . . p. 27 8 Inserção de processo em determinada posição na composição seqüencial . . . p. 27 9 Remoção de processos da composição seqüencial . . . p. 27 10 Construtor de composição Paralelo. . . p. 28 11 Exemplo de uma composição Paralela. . . p. 28 12 Adição de um novo processo a composição Paralela. . . p. 28 13 Adição de vários novos processos a composição Paralela. . . p. 28 14 Remoção de processos da composição seqüencial. . . p. 29 15 Construtor de composição Paralelo com prioridade. . . p. 29 16 Exemplo de uma composição Paralelo com prioridade. . . p. 30 17 Exemplo de uma construção aninhada da composição Paralela com

priori-dade, aumentando os níveis de prioridade. . . p. 31 18 Adição de um novo processo a composição Paralela com prioridade. . . p. 31 19 Adição de vários novos processos a composição Paralela com prioridade. . . p. 31 20 Inserção de processo em determinada posição na composição Paralela com

prioridade. . . p. 32 21 Remoção de processos da composição Paralela com prioridade. . . p. 32

(5)

23 Exemplo de uma composição Alternativa. . . p. 32 24 Inserção de um novo Guard na composição Alternativa. . . p. 33 25 Inserção de vários Guard na composição Alternativa. . . p. 33 26 Remoção de Guard da composição Alternativa. . . p. 33 27 Declaração de um objeto da classe PriAlternative. . . p. 33 28 Exemplo de uma composição Alternativa com prioridade, . . . p. 34 29 Adição de um novo guarda a composição PriAlternative. . . p. 34 30 Adição de vários guardas a composição PriAlternative. . . p. 34 31 Adição de vários guardas a composição PriAlternative. . . p. 34 32 Adição de vários guardas a composição PriAlternative. . . p. 35 33 Exemplo de composição aninhada, composição paralela com duas composições

seqüenciais aninhadas. . . p. 35 34 Exemplo de composição aninhada, composição alternativa com duas

com-posições aninhadas, uma paralela e outa seqüencial. . . p. 36 35 Criando um objeto da classe Guard. . . p. 36 36 Execução condicional de um Guard. . . p. 37 37 Declaração de um Guarda condicional. . . p. 37 38 Declaração de um Guarda incondicional. . . p. 37 39 Parte 1 da classe Mesa, da aplicação Jantar dos filósofos implementada em

CTJ. . . p. 47 40 Parte 2 da classe Mesa, da aplicação Jantar dos filósofos implementada em

CTJ. . . p. 48 41 Parte 3 da classe Mesa, da aplicação Jantar dos filósofos implementada em

CTJ. . . p. 49 42 Classe Filosofo, da aplicação Jantar dos filósofos implementada em CTJ. . . . p. 50 43 Classe JantarDosFilosofos, da aplicação Jantar dos filósofos implementada

(6)

45 Classe Filosofo, da aplicação Jantar dos filósofos implementada em Java. . . p. 53 46 Classe Main, da aplicação Jantar dos filósofos implementada em Java. . . p. 54

(7)

Lista de Figuras 1 Introdução p. 10 1.1 Tema . . . p. 11 1.2 Limitações do tema . . . p. 12 1.3 Objetivo geral . . . p. 12 1.4 Desenvolvimento do trabalho . . . p. 12 1.5 Motivação . . . p. 13

2 Communicating Sequencial Process (CSP) p. 14 2.1 Diferenças entre threads de Java e processos CSP . . . p. 15 2.2 Primitivas de sincronização . . . p. 16

3 Processos CTJ p. 18

3.1 Estados de transição . . . p. 19 3.1.1 Estados de transição da thread. . . p. 19 3.1.2 Estados de transição de um processo CTJ. . . p. 20

4 Canais CTJ p. 22

(8)

5 Utilizando CTJ p. 25 5.1 Criando processos em Java . . . p. 25 5.2 Composição de processos . . . p. 26 5.2.1 Construtor de Composição Seqüencial - Sequencial . . . p. 26 5.2.2 Construtor de Composição Paralelo - Parallel . . . p. 28 5.2.3 Construtor de Composição Paralelo baseado em Prioridade -

PriPar-allel . . . p. 29 5.2.4 Construtor de Composição Alternativo - Alternative . . . p. 32 5.2.5 Construtor de Composição Alternativo baseado em Prioridade -

Pri-Alternative . . . p. 33 5.2.6 Construtor de Composição Aninhados . . . p. 35 5.3 Guardas Condicionais e Incondicionais . . . p. 36

6 Validação do programas CSP p. 38

7 Comparação entre aplicações utlizando CTJ e Java p. 40

8 Conclusão p. 45

Apêndice A -- Classe MesaCTJ p. 47

Apêndice B -- Classe FilosofoCTJ p. 50

(9)

Apêndice E -- Classe FilosofoJava p. 53

Apêndice F -- Classe MainJava p. 54

(10)

1 Interface de processo (HILDERINK et al., 1997). . . p. 18

2 Diagrama de transição de estados das threads. . . p. 20 3 Diagrama de transição de estados dos processos (HILDERINK et al., 1997). . . . p. 21

4 Interface de canal CTJ (HILDERINK et al., 1997). . . p. 23

5 Arquitetura do cálculo de verificação (KLEBANOV et al., 2005). . . p. 39

6 Rede de processos do Jantar dos Filósofos. . . p. 41 7 Duas threads tomando posse de regiões críticas distintas. . . p. 42 8 Deadlock gerado pela tentativa de das thread à regiões críticas bloqueadas. . . p. 42 9 Rede de processos do exemplo de não ocorrência de deadlock em CTJ. . . p. 43 10 Processo1 acessando o recurso 1, e o Processo2 acessando o recurso 2. . . p. 43 11 Processo1 acessando o recurso 2, e o Processo2 acessando o recurso 1, sem a

(11)

1

Introdução

Apesar da implementação de aplicações multithread em Java não ser difícil, a sua utilização de forma correta requer muito esforço. O raciocínio sobre aplicações utilizando threads, pode ser diferente para cada programa construído. Threads são as principais causas no aumento na complexidade das aplicações concorrentes. Como resultado, programas multithread são muito mais suscetíveis a erros.

A sincronização e o escalonamento de Java são mal especificados, gerando um desempenho de tempo real insatisfatório(HILDERINK et al., 1997). A linguagem Java deixa a cargo do sistema

operacional a especificação das regras para a sincronização e escalonamento, podendo resultar em desempenhos diferentes ao utilizar sistemas operacionais distintos (SUN, 2006e). O

sin-cronismo, que é baseado em monitores e transferência de dados, é implementado através do compartilhamento de memória (KLEBANOV et al., 2005).

O núcleo do modelo de threads de Java é derivado dos conceitos tradicionais de multithread-ing. A literatura sobre threads e sincronização de threads é na maior parte sobre a compreensão do sistema operacional ou dos conceitos de baixo nível de multithreading (HILDERINK et al.,

1997). A liberdade dada pelas APIs para a utilização de threads aumentam os perigos de cor-rida de hazards, deadlock, livelock, starvation. O programador deve ter muito cuidado, e deve aplicar um grande conjunto de regras, normas ou padrões de projeto.

Corrida de hazards (race hazards) é uma das conseqüências da sincronização incorreta, gerando a disputa de recursos do sistema por múltiplas threads, é o corrompimento dos dados, onde parte dos dados é modificado por uma thread e parte por outra. Deadlock é uma situação na qual uma thread é bloqueada para sempre porque está esperando certa condição se tornar

(12)

verdadeira, mas a condição é impedida de se tornar verdadeira, pois o trecho de código, que está de posse de um thread, que deveria habilitar está condição também está bloqueado. Um livelock, diferente do deadlock, acontece quando as threads estão em execução, mas nada acontece, isso ocorre geralmente quando duas threads então trabalhando com intenções contrarias, o que é feito por uma thread é desfeito pela outra. Starvation ocorre quando uma thread é bloqueada por tentar o acesso a um recurso que está sendo utilizado por outra thread, porém a thread bloqueada nunca irá executar novamente por não ter acesso ao recurso desejado, pois o mesmo não será liberado pela thread que o detém, ou por outras threads de maior prioridade também tentar o acesso a esse recurso, starvation é uma conseqüência do fato que as primitivas wait/notify da linguagem Java não garantem a "sobrevivência" da thread.

Analisar um programa multithread e eliminar os erros de estados das threads pode ser ex-tremamente difícil utilizando uma interface de programação (API). Métodos teóricos foram desenvolvidos para analisar o comportamento de aplicações multithread e eliminar os perigos gerados por ela, porém são pouco eficazes e nada práticos. Para tentar evitar os problemas gerados pela programação multithread de Java, pode-se utilizar o conceito de Communicating Seqüencial Process (CSP) (HOARE, 1983).

1.1

Tema

A criação de sistemas de tempo real está cada vez mais em evidência, sendo necessário para isso a utilização de softwares que possam agir de forma concorrente, utilizando-se o conceito de threads pra obtêr la.

Com a liberdade e flexibilidade dada pelas APIs, o desenvolvimento de aplicações uti-lizando processamento multithread ficou simplificado, resultando em sistemas defeituosos, de-vido ao perigo da ocorrência de condições indesejadas, tais como corridas de hazards, deadlock, livelock e starvation (negação de serviços infinita).

Desta forma, deve se tomar cuidado com cada tipo de sincronismo utilizado nas threads de uma aplicação, utilizando um conjunto de regras, normas e padrões de projeto.

(13)

1.2

Limitações do tema

O trabalho desenvolvido usa o pacote Communicating Threads for Java (CTJ) (HILDERINK,

2006), threads que se comunicam em Java, que implementa o modelo CSP (tex citação car hoare) em Java, que inclui padrões de threads muito mais simples e confiáveis que o modelo das threads de Java. O pacote CTJ fornece um pequeno conjunto de padrões de projeto que é suficiente para programação concorrente em Java. Um importante avanço deste pacote é que o programador pode usar um grande conjunto de regras para eliminar os hazards, deadlocks, livelock, starvation etc. durante o projeto e implementação.

A teoria de processos seqüenciais de comunicação (Communicating Sequential Process -CSP) oferece uma notação matemática pra descrever padrões de comunicação por expressões algébricas que contém evidências formais para análise, verificando e eliminando as condições indesejáveis na utilização de threads. Esse modelo foi provado bem-sucedido para criação de softwares concorrentes para tempo real e sistemas embarcados.

1.3

Objetivo geral

Desenvolver um problema computacional clássico utilizando Java e a teoria de CSP, fazendo comparações entre a mesma aplicação utilizando apenas Java, verificando a viabilidade da uti-lização da teoria CSP aliada a Java.

1.4

Desenvolvimento do trabalho

• Pesquisa e buscas de referências na literatura para o estudo da teoria de CSP, de Charles Antony Richard Hoare.

• Pesquisa e busca de referências na literatura por pacotes que implementam a teoria de CSP em Java.

(14)

com códigos que utilizam multithread em Java.

1.5

Motivação

Devido à grande disseminação da linguagem Java, e a sua vasta área de atuação, verificar se a utilização do pacote CTJ, que implementa a teoria de CSP, elimina o grande problema na utilização de múltiplas threads, que é a geração de hazardas, deadlocks, livelock, starvation etc. Pretendendo difundir a teoria de CSP, sua funcionalidade na prática, e os custos para a sua utilização na resolução de problemas computacionais.

(15)

2

Communicating Sequencial Process

(CSP)

Communicating Sequential Processes (CSP) é uma teoria matemática para descrição de computação concorrente e distribuída, que pode ser usada para desenvolver aplicações multi-thread que são garantidamente livres dos problemas comuns de concorrência, e o mais impor-tante, pode ser provado através de regras matemáticas (HOARE, 1983). O seu comportamento é

baseado em teoria matemática, a qual é descrita por um conjunto de regras algébricas.

Um programa CSP é um conjunto de processos que se interconectam através de canais, a única forma de interação entre processos, utilizando métodos de escrita e leitura para a troca de informações com o meio de comunicação. A comunicação ocorre de forma síncrona, com o fluxo direcional da informação. Um processo que executa uma primitiva de comunicação (escrita ou leitura) é bloqueado até que o processo com o qual está tentando se comunicar executa a primitiva correspondente. Comandos de escrita e leitura são ditos correspondentes.

A teoria matemática fornece uma definição robusta do conceito de processo, e dos oper-adores nos quais os processos são construídos. Essas definições são a base para as leis algébri-cas, para a implementação e a prova das regras que garantem a exatidão das aplicações.

A teoria de CSP especifica inteiramente o comportamento da sincronização e escalona-mento das threads em alto nível de abstração, que é baseado em processos, em composições e primitivas de sincronização.

Existem dois pacotes principais que implementam o modelo CSP para a linguagem Java, o Communicating Sequential Processes for Java (JCSP)(WELCH, 2006) desenvolvido pelo

(16)

Profes-sor Peter Welch e por Paul Austin na Universidade de Kent, da Inglaterra, e o Communicating Threads for Java (CTJ)(HILDERINK, 2006) desenvolvido por Gerald Hilderinks na Universidade

de Twente, dos Países Baixos.

Ambos são baseados nas primitivas de CSP, mas possuem diferenças em seus projetos e objetivos. JCSP foi criado como uma alternativa mais lógica a API de concorrência de Java, combinando as capacidades de CSP com a aceitação da linguagem Java, utilizado na progra-mação concorrente em geral. Já CTJ é um pacote criado para o desenvolvimento de aplicações embarcadas e de tempo real. Ambos têm como objetivo tornar a programação concorrente mais segura, garantindo um funcionamento adequado da aplicação, porém sem aumentar a sua di-ficuldade. O pacote CTJ controla diretamente as threads, pois possui seu próprio kernel que especifica o comportamento do sincronismo e escalonamento das threads, tornando-se indepen-dente de plataforma e de comportamento previsível (característica de programação tempo real). Já JCSP usa as construções de concorrência da linguagem, como os métodos wait, notify e a cláusula synchronized.

Uma vantagem importante é que o programador pode aplicar um conjunto de regras para eliminar corrida de hazards, deadlock, livelock, starvation, durante o projeto.

O pacote escolhido neste trabalho foi o CTJ, pois especifica o comportamento do escalon-amento e do sincronismo, assumindo o controle das threads, não mais deixando para o sistema operacional, e dando uma maior previsibilidade ao programa. Compreender o conceito não re-quer muito conhecimento sobre a teoria de CSP. O pacote de CTJ constrói uma ponte entre a teoria e a prática de CSP.

2.1

Diferenças entre threads de Java e processos CSP

Os termos thread e processo são muito próximos. Um processo encapsula seus dados e métodos, da mesma forma que os objetos, e encapsulam também uma ou mais threads de con-trole. Ao atribuir uma thread de controle a um objeto passivo será criado um objeto ativo, tornando-o um processo.

(17)

Um processo CSP é um objeto ativo cujas instruções são executadas por uma thread de controle que está encapsulada no processo. Canais são objetos passivos e essenciais entre pro-cessos, pois são utilizados para o envio e recebimento de mensagens. Variáveis devem ser mantidas dentro do espaço de memória do processo, e não precisam ser sincronizadas, pois cada processo possui seu endereçamento. Apenas dados de leitura podem ser compartilhados entre os processos.

Processos podem iniciar processos, mas não podem controlar diretamente o processo filho. Além de seguro, a depuração dos processos é menos complexa que seguir a execução de threads baseadas no modelo utilizado por Java.

O comportamento das threads de Java é pouco específico e fortemente dependente dos padrões adotados pelo sistema operacional (SUN, 2006e). Então, o comportamento das threads

em diferentes máquinas virtuais Java (JVM) pode variar, enquanto o comportamento dos pro-cessos de CTJ deverá ser semelhante em qualquer máquina virtual Java, JVM.

A principal diferença entre threads e processos é que os processos são tipicamente indepen-dentes (espaço de endereçamento diferente), um processo não sabe da existência de qualquer outro processo, estes interagindo apenas com os mecanismos de comunicação, os canais. Já as threads tipicamente dividem o mesmo espaço de endereçamento e podem compartilhar objetos em memória e recursos do sistema diretamente.

2.2

Primitivas de sincronização

Em Java, um objeto pode ter mais de uma thread. Threads que podem operar simultanea-mente dados compartilhados devem ser sincronizadas para prevenir corrida de hazard, que pode resultar em dados corrompidos ou estados inválidos (travamento do sistema). O usuário deve controlar cada thread utilizando vários métodos, os quais devem ser usados de forma apropri-ada. Esse conjunto de métodos (ver as classes java.lang.Thread (SUN, 2006c) e java.lang.Object

(SUN, 2006a) fornecem um conceito básico e flexível de multithreads. A clausula synchronized

(18)

que tem acesso a dados compartilhados e pode ser utilizado por mais de uma thread, permitindo que apenas uma thread tenha acesso a essa região por vez). Os métodos wait()/notify() fazem o enfileiramento condicional das threads que necessitam utilizar as regiões críticas em comum. A construção de um monitor de sincronização envolve manter sob observação um ou mais méto-dos. Não é fácil determinar quais métodos ou trechos de código devem ser sincronizaméto-dos. De-terminados padrões de projeto podem resolver esse problema (HILDERINK et al., 1997), mas eles

podem tornar o programa mais complexo que o necessário. Além disso, inserir sincronização entre dois métodos corretamente pode ser difícil e suscetível a erros.

Já em CSP, canais são objetos especiais que gerenciam a comunicação entre processos, de uma forma pré-definida. Canais são objetos passivos situados entre os processos e que contro-lam a sincronização, o escalonamento, e transferência de mensagens entre processos. Comu-nicação por canais é considerada thread-safe (trecho de código que pode ser executado com segurança em um ambiente multithread), mais confiável e rápido. Além disso, o programador estará livre de implementar a sincronização e o escalonamento. Canais CSP são inicializados sem buffer e são sincronizados de acordo com o princípio do rendezvous (o processo escritor espera até que o processo leitor esteja pronto ou o processo leitor espera até que o escritor esteja pronto). Os canais CTJ permitem zero ou mais buffers, de acordo com as suas necessidades. A simplicidade de se utilizar canais ao invés de monitores é uma importante motivação para a utilização do conceito de CSP.

(19)

3

Processos CTJ

Processos que executam paralelamente não ”vêem” um ao outro. Cada processo enxerga apenas seus canais, e isso é tudo que eles precisam. Em Java, um processo pai cria um processo filho e inicia a execução do mesmo com o método run(), não havendo mais nenhuma interação direta entre os dois. A interface de processo CTJ (HILDERINK, 2006h) é especificada por uma

interface de processo passiva, especificando o método run(), e uma interface de processo ativa, especificando o conjunto de canais entrada/saída que serão usados pelo processo.

A interface de processo pode ser derivada de modelos data-flow (a comunicação é feita pela emissão das mensagens aos receptores), que são grafos rotulados e orientados, compostos pelos processos (nodos) e pelos canais (arestas). Processos são conectados por arestas e elas especificam a direção das mensagens. A figura 1 mostra um grafo de uma interface de um processo ativo com um canal de entrada e outro de saída.

(20)

3.1

Estados de transição

Para cada processador haverá apenas uma thread em execução em um determinado mo-mento. Um sistema multiprocessador com n processadores poderá ter n threads executando simultaneamente. Entretanto um sistema uniprocessador pode executar múltiplas threads uti-lizando um escalonador. Os estados das threads e processos não precisam ser iguais, embora eles possam rodar no mesmo sistema. A seguir serão mostradas as diferenças entre estados de transição de threads e processos.

3.1.1

Estados de transição da thread.

Uma thread pode estar em um dos seguintes estados (SUN, 2006d):

• new : a thread foi instanciada, porém ainda não está em execução; • runnable: thread já em execução, de posse do processador;

• blocked: a thread está bloqueada, esperando ser liberada pelo monitor (método syn-cronyzed);

• waiting: a thread está esperando indefinidamente que outra thread execute uma ação, um signal;

• timed waiting: a thread está esperando que outra thread execute uma ação até um deter-minado tempo;

• terminated (tex): a thread terá terminado sua execução.

O diagrama de transição de estados mostrado na figura 2 na próxima página exemplifica o modelo de transição das threads.

(21)

Figura 2: Diagrama de transição de estados das threads.

3.1.2

Estados de transição de um processo CTJ.

Um processo pode estar em um dos seguintes estados (HILDERINK et al., 1997):

• instantiated: o processo pode ter sido criado ou terminou com sucesso, nenhuma thread está atribuída ao processo;

• running: a thread de controle foi alocada a um processo e então o processo foi ativado; • preempted: o processo foi preemptado, ou seja, retirado do processador por um de maior

prioridade, o processo está pronto para execução, mas não está executando;

• waiting: a thread de controle está inativa ou bloqueada e está esperando para ser notificada (voltar ao processador);

• garbage: o processo terminou e não será atribuído a mais nenhuma thread, o garbage collector de Java excluirá o objeto.

O diagrama de transição de estados mostrado na figura 3 na página seguinte ilustra as transições de estados de um processo:

(22)

Figura 3: Diagrama de transição de estados dos processos (HILDERINK et al., 1997).

O diagrama de transição de estados de processo distingue entre escalonadores preemptivos e não preemptivos. Um processo que é preemptado por um processo de maior prioridade deve ser temporariamente interrompido e ir para o estado preempted. Um processo preemptado passará para o estado running caso não exista outro processo com prioridade maior esperando para executar no estado preempted.

(23)

4

Canais CTJ

A interface de canais do pacote CTJ (HILDERINK, 2006b) é simples, contendo uma interface

para canais de entrada, que especifica o método read(), e uma interface para canais de saída que especifica o método write(). Processos comunicam-se com outros processos utilizando leitura ou escrita nos canais compartilhados invocando os métodos read() e write(). Canais CTJ permitem também múltiplas escritas e leituras.

Para evitar a perda de dados, os canais de comunicação são sincronizados. Isso significa que se o emissor envia mensagem antes do receptor estar pronto para receber, o emissor será bloqueado. Da mesma forma, se o receptor tentar receber antes que o emissor tenha enviado uma mensagem, o receptor será bloqueado. Quando ambos estão prontos, os dados podem ser transmitidos. Os Canais do pacote CTJ enviam, por default, mensagem por valor, evitando o compartilhamento de dados, embora também seja possível enviar mensagem por referencia. O envio de mensagens por valor faz com que tanto canais de comunicação interna (utilizando o mesmo disco físico) quanto canais externos (discos físicos diferentes) tenham o mesmo trata-mento. Os canais do JCSP (P.D.AUSTIN, 2006) enviam mensagens por referência, menos para a

primitiva int, sendo necessário um tratamento especial para a transmissão de dados em canais externos, pois não compartilham o mesmo espaço de endereçamento.

Comunicação via canais fornece um estrutura independente de hardware e uma estrutura dependente de hardware. Ambas as estruturas são conectadas por uma interface de canal muito simples, figura 4 na próxima página.

(24)

Figura 4: Interface de canal CTJ (HILDERINK et al., 1997).

4.1

Independente de hardware:

A comunicação via canais fornece uma plataforma independente de estrutura, onde os pro-cessos podem ser alocados no mesmo sistema ou distribuídos em outros sistemas. Os propro-cessos nunca acessam diretamente o hardware, eles podem apenas se comunicar com seu ambiente por meio dos canais. Como resultado, os processos não sabem quais processos estão do outro lado do canal e não sabem que hardware está entre os dois.

4.2

Dependente de hardware:

Canais podem estabelecer um link entre dois ou mais sistemas via algum hardware. Ob-jetos especiais chamados linkdrivers podem ser inseridos no canal. Os linkdrivers controlam o hardware e são as únicas partes da aplicação que possuem dependência de hardware. A es-trutura do linkdriver é definida como abstrata, e pode ser estendida conforme a necessidade, sem influenciar o projeto ou as estruturas independentes de hardware. A estrutura do linkdriver também fornece tratamento de interrupções.

Todos os processos que não utilizam linkdriver são totalmente independentes de hardware. Os processos que utilizam linkdrivers podem ser mais ou menos dependentes do hardware, pois o linkdriver representa diretamente o hardware. A utilização de linkdriver facilita a

(25)

portabil-idade das aplicações, pois caso haja a necessportabil-idade da utilização de outro hardware, as mod-ificações no projeto serão mínimas, apenas substituindo os atuais linkdrivers por uma versão estendida para o novo hardware.

Os linkdrivers fazem a comunicação com a arquitetura de hardware, recursos do sistema e fornecem um comportamento opcional, determinando a forma de comunicação, seja interna (através da memória utilizando rendezvous ou buffers) ou externa (via periféricos como RS232, PCI ou TCP/IP). Podem ser inseridos em um canal fornecendo os protocolos de comunicação para a transferência de dados através do hardware. A combinação entre canais e linkdrivers é poderosa, pois fornece uma estrutura para comunicação tanto interna como externa. Como resultado os processos se tornam altamente independentes do meio físico.

Um canal pode facilmente estender um linkdriver, como mostra a algoritmo 1. Algoritmo 1 Canal estendendo um linkdriver.

(26)

5

Utilizando CTJ

Essa seção descreve como criar processos e composição de processos em Java, utilizando o pacote CTJ.

5.1

Criando processos em Java

Um processo é definido pela interface csp.lang.Process (HILDERINK, 2006h). Uma classe

de processo deve implementar à interface csp.lang.Process, que é muito semelhante a interface java.lang.Runnable(SUN, 2006b). O método run() implementa o corpo runnable do processo,

que será invocado por outro processo e executará uma tarefa seqüencial. Algoritmo 2 Exemplo de uma classe que implementa a interface de Processo.

O construtor do processo deve especificar os canais de entrada e saída e os parâmetros para inicialização do processo. Não mais que um processo pode invocar o método run() ao mesmo tempo. O método run() só poderá começar sua execução se os recursos necessários estiverem disponíveis, para um funcionamento confiável. Na instanciação de um processo, seu construtor deve inicializar todos os recursos, como os canais de entrada e saída e os parâmetros, antes que

(27)

o método run() seja chamado.

Algoritmo 3 Criando e executando um processo.

5.2

Composição de processos

Basicamente, processos são executados quando o método run()(tex) é invocado. O processo que o chamou espera até o método run() retornar com sucesso.

A teoria de CSP descreve composições comuns nas quais os processos são executados, ou seja, processos podem executar em seqüência ou em paralelo. Nessa seção serão apresentadas as seguintes construções de composições: Seqüencial (5.2.1), Paralelo (5.2.2), Pararelo com prioridade (5.2.3), Alternativo (5.2.4) e Alternativo com prioridade (5.2.5).

5.2.1

Construtor de Composição Seqüencial -

Sequencial

O construtor de composição seqüencial executa apenas um processo por vez. O processo de composição seqüencial terminará quando todos os processos internos forem finalizados. O construtor de composições seqüencial é criado pela classe Sequential (HILDERINK, 2006g). O

objeto seqüencial é um processo.

Algoritmo 4 Construtor de composição seqüencial.

No algoritmo 4 o argumento processos é um Array de processos. O construtor inicia quando invocado seu método run().

(28)

Algoritmo 5 Exemplo de uma composição seqüencial.

O Processo2 será executado após o Processo1 ter finalizado sua execução com sucesso, e o Processo3após a execução bem sucedida do Processo2. O processo de composição sequencial é bem sucedido quando todos os processos foram executados com sucesso.

Novos processos podem ser adicionados no fim lista, como mostra o algoritmo 6: Algoritmo 6 Adição de um novo processo à composição seqüencial.

ou ainda ...

Algoritmo 7 Adição de vários novos processos a composição seqüencial.

E novos processos podem ser inseridos em uma determinada posição na lista como no algoritmo 8:

Algoritmo 8 Inserção de processo em determinada posição na composição seqüencial .

Podem ser removidos da lista de processos 9:

Algoritmo 9 Remoção de processos da composição seqüencial .

Os métodos mencionados acima só podem ser executados pela instância que criou o pro-cesso de composição, quando este não estiver em execução. Essas restrições asseguram a con-fiabilidade e segurança para o construtor.

(29)

5.2.2

Construtor de Composição Paralelo -

Parallel

O construtor de composição paralelo executa processos em paralelo. O construtor termina quando todos os processos forem finalizados com sucesso. O construtor é criado pela classe Parallel(HILDERINK, 2006d), sendo esse objeto um processo.

Algoritmo 10 Construtor de composição Paralelo.

No algoritmo 10 o argumento processos é um Array de processos. O construtor é inicial-izado quando o método run() é invocado.

O exemplo 11 mostra uma composição em paralelo de três processos. Algoritmo 11 Exemplo de uma composição Paralela.

Os processos Processo1, Processo2 e Processo3 serão executados em paralelo. Cada um terá uma thread de controle interno com a mesma prioridade de execução. O processo paralelo será finalizado com sucesso se todos seus processos internos forem bem sucedidos.

Novos processos podem ser inseridos a lista, algoritmo 12: Algoritmo 12 Adição de um novo processo a composição Paralela.

ou ainda como no algoritmo 13:

Algoritmo 13 Adição de vários novos processos a composição Paralela.

(30)

Algoritmo 14 Remoção de processos da composição seqüencial.

5.2.3

Construtor de Composição Paralelo baseado em Prioridade -

Pri-Parallel

O construtor de composição paralela com prioridades (HILDERINK, 2006f) estende o

con-strutor de composição paralela, porém agora com prioridades. A cada processo do concon-strutor priparalelo será dada uma prioridade, enquanto que os processos do construtor de composição paralela recebem a mesma prioridade. O primeiro processo da lista receberá a maior priori-dade, e o último receberá a menor prioridade da lista de processos. O objeto priparalelo é um processo.

Atualmente, o número máximo de prioridade por objeto priparalelo é 8, onde 7 são para os processos e um é reservado para as tasks idle, skip e garbage collector (ainda não implemen-tado). Processos priparalelos podem ser inicializados aninhados (ver 5.2.6 na página 35), assim, podendo aumentar o número de processos com prioridades.

Os processos são executados por prioridade, tais prioridades são definidas pela ordem de inserção na lista de processos, não sendo possível fazer a edição das mesmas para os processos já adicionados.

A classe PriParallel cria um processo paralelo baseado em prioridade. Algoritmo 15 Construtor de composição Paralelo com prioridade.

O argumento processos(tex) é uma Array(tex) de Process. O construtor é iniciado pela chamada do método run()(tex).

(31)

Algoritmo 16 Exemplo de uma composição Paralelo com prioridade.

Os processos Processo1, Processo2 e Processo3 serão executados em paralelo com priori-dades sucessivas. O Processo1 (de índice 0) tem a maior prioridade. Todos os processos da lista que possuem índice 6 ou maior, compartilham a menor prioridade, 6. O processo priParalelo termina com sucesso quando todos os processos internos são executados com sucesso. Para au-mentar o número máximo de prioridades é possível criar novos processos PriParellel aninhados ( 5.2.6 na página 35) ao processo já criado. O exemplo 17 mostra um construtor priparalelo com 28 níveis de prioridade.

(32)

Algoritmo 17 Exemplo de uma construção aninhada da composição Paralela com prioridade, aumentando os níveis de prioridade.

Novos processos podem ser adicionados em tempo de execução:

Algoritmo 18 Adição de um novo processo a composição Paralela com prioridade.

Ou ainda:

Algoritmo 19 Adição de vários novos processos a composição Paralela com prioridade.

Processos podem ser inseridos em determinada posição da lista de processos (recebendo a prioridade de tal índice):

(33)

Algoritmo 20 Inserção de processo em determinada posição na composição Paralela com pri-oridade.

Processos podem ser removidos:

Algoritmo 21 Remoção de processos da composição Paralela com prioridade.

A ordem das prioridades será atualizada com a inserção ou remoção de um processo.

5.2.4

Construtor de Composição Alternativo -

Alternative

O construtor de composição alternativa é composto por guardas, sendo cada guarda um pro-cesso. Assim que um guarda fica pronto, ele é executado pelo processo principal. O construtor da composição alternativa é definida pela classe Alternative (HILDERINK, 2006a).

Algoritmo 22 Construtor de composição Alternativo.

O argumento guardas é um Array de objetos guardas (ver capítulo 5.3 na página 36). Um objeto guarda é uma instância da classe Guard (HILDERINK, 2006c). O processo construtor é

iniciado pelo método run().

O exemplo 23 mostra uma composição alternativa para três processos. Algoritmo 23 Exemplo de uma composição Alternativa.

O processo alternativo espera pelo menos um dos guardas estar pronto, mas termina quando um dos processos for selecionado e finalizado com sucesso. O guarda que possui o processo de

(34)

índice X, será selecionado quando o canal espeficado em seu construtor estiver pronto, sendo este canal o canal de entrada do processo associado ao guarda. Se mais que um guarda estiver pronto para executar eles serão selecionados se forma randômica.

Novos guardas podem ser inseridos em tempo de execução: Algoritmo 24 Inserção de um novo Guard na composição Alternativa.

ou ainda:

Algoritmo 25 Inserção de vários Guard na composição Alternativa.

E podem ser removidos também:

Algoritmo 26 Remoção de Guard da composição Alternativa.

5.2.5

Construtor de Composição Alternativo baseado em Prioridade -

Pri-Alternative

A classe PriAlternative (HILDERINK, 2006e) cria um construtor de composição alternativo

baseado em prioridade. Ela estende a classe Alternative e substitui o mecanismo de escolha randômica por um baseado em prioridade. O objeto priAlternativo é um processo. O construtor prialternativo é semelhante do alternativo...

Algoritmo 27 Declaração de um objeto da classe PriAlternative.

O argumento guardas é um Array de objetos Guarda. Um objeto guarda é uma instância da classe Guard (HILDERINK, 2006c). O construtor PriAlternativo é inicializado pelo método

(35)

O algoritmo 28 mostra uma composição PriAlternative para três processos Algoritmo 28 Exemplo de uma composição Alternativa com prioridade,

O processo priAlternativo espera até pelo menos um guarda estar pronto, mas termina com sucesso quando um dos processos internos é selecionado e finalizado com sucesso. O guarda que possui o processo de índice X, será selecionado quando o canal espeficado em seu construtor estiver pronto, sendo este canal o canal de entrada do processo associado ao guarda, se mais de um guarda estiver pronto, o guarda de maior prioridade (menor índice) será selecionado. O processo pertencente ao guarda selecionado será executado.

Um novo guarda pode ser adicionado em tempo de execução, algoritmo 29. Algoritmo 29 Adição de um novo guarda a composição PriAlternative.

Ou vários guardas podem ser inseridos na composição, algoritmo 30. Algoritmo 30 Adição de vários guardas a composição PriAlternative.

Um guarda pode ser inserido por índice na composição PriAlternativa, conforme algo-ritmo 31.

(36)

Sendo a prioridade do guarda inserido igual ao seu índice, e as prioridades dos guardas com índice maior serão incrementadas.

É possível a remoção de guardas, conforme mostrado no algoritmo 32. Algoritmo 32 Adição de vários guardas a composição PriAlternative.

5.2.6

Construtor de Composição Aninhados

Os processos de composição Seqüencial ( 5.2.1 na página 26), Paralela ( 5.2.2 na página 28), Paralela com prioridade ( 5.2.3 na página 29), Alternativa ((5.2.4)) e Alternativa com prioridade ((5.2.5)) aceitam processos de composição aninhados. Duas composições seqüenciais podem executar em paralelo, como no algoritmo 33.

Algoritmo 33 Exemplo de composição aninhada, composição paralela com duas composições seqüenciais aninhadas.

Ou uma composição alternativa que optará entre a execução da composição paralela ou seqüencial, como no algoritmo (34).

(37)

Algoritmo 34 Exemplo de composição aninhada, composição alternativa com duas com-posições aninhadas, uma paralela e outa seqüencial.

5.3

Guardas Condicionais e Incondicionais

O objeto guarda é uma instância da classe Guard (HILDERINK, 2006c), que controla um

processo, que estará pronto quando o construtor receber a primeira ocorrência de comunicação no canal de entrada desse processo. O processo é controlado por entradas, ou seja, apenas os canais de entrada podem ser monitorados pelos processos. O controle dos canais de saída não foi implementado porque isso resultaria em uma penalidade na performance do canal.

Um objeto guarda pode ser declarado como no algotimo 35. Algoritmo 35 Criando um objeto da classe Guard.

O guarda se torna true quando o argumento canal (interface de entrada) está pronto e tem dados disponíveis para leitura. O guarda em si não é um processo, mas um objeto passivo que controla os processos. O objeto alternativo (ver 5.2.3 na página 29) verifica se todos os guardas estão disponíveis e espera pelo menos até que um canal fique pronto, então o processo que pertence ao guarda ativado pode ser selecionado e executado. Um guarda habilitado que sempre é executado no construtor alternativo é chamado de incondicional. Um guarda também

(38)

pode ser condicional, isto é, será habilitado (participará do construtor alternativo) se alguma condição for verdadeira, ou será desabilitado (não participa do construtor) e o processo não é executado.

Algoritmo 36 Execução condicional de um Guard.

Se a condição for verdadeira o guarda verificará o canal, porém, se for falsa, o guarda não executará e o processo não será selecionado. Se o processo for selecionado, deverá ler os dados do canal.

Um guarda pode ser declarado com uma variável condicional (algoritmo 37). Algoritmo 37 Declaração de um Guarda condicional.

Ou sem a variável (algoritmo 38).

Algoritmo 38 Declaração de um Guarda incondicional.

(39)

6

Validação do programas CSP

O comportamento da linguagem CSP é baseado em teoria matemática, a qual é descrita por um conjunto de leis algébricas. Sendo assim, o comportamento de uma aplicação CSP pode ser validado utilizando-se regras matemáticas, garantindo a ausência de problemas comuns a programação concorrente. Existem várias ferramentas que fazem a validação de aplicações CSP, uma delas é o Deadlock Checker (MARTIN; JASSIM, 2007), que verifica a existência de deadlock

e livelock a partir da construção de um sistema de transições de toda a rede de processos, verificando cada estado global em busca de deadlock (MARTIN; JASSIM, 1997).

Já os pacotes que implementam o modelo CSP em Java, apenas o JCSP dá suporte à val-idação de código, através de um sistema de prova, que estende os cálculos da tecnologia Java Card (SUN, 2007), que corresponde à linguagem de programação Java sem threads e é usada

para programação de smartcards. Uma parte importante do sistema de prova é o cálculo da lógica do programa, que é feito através do JavaCard Dynamic Logic (JavaCardDL) que foi desenvolvido no projeto KeY. A ferramenta KeY é um sistema para verificação de programas Java Card, podendo também verificar programas Java sem threads (KLEBANOV et al., 2005).

A figura 5 na página seguinte mostra a arquitetura de verificação do sistema, a qual consiste em quatro componentes. A verificação do código possui quatro passos:

1. O primeiro passo executa instruções JavaCard até encontrar uma chamada para alguma biblioteca JCSP. Isso é executado pelos padrões de cálculo do programa KeY. Devido à comunicação entre processos ser através de canais, de forma explicita, não existe interfer-ência no código seqüencial de um processo. Do ponto de vista CSP, um código seqüencial

(40)

pode ser visto como um processo que produz apenas eventos internos.

2. A segunda parte, executando em paralelo com a primeira, substitui as bibliotecas JCSP utilizadas no programa por seus modelos em CSP.

3. O passo 3 reescreve o programa, transformando os processo em uma forma normal que permite uma dedução mais simples dos passos de um processo.

4. No quarto passo, as asserções são avaliadas segundo os possíveis comportamentos dos processos.

Figura 5: Arquitetura do cálculo de verificação (KLEBANOV et al., 2005).

Com a forma normal dos processos montada ocorrem duas fases de validação deste código. Na primeira parte a rede de processos (uma espécie de grafo que possui os processos como nodos e os canais como arestas) é montada. O segundo passo envolve a execução desta rede de processos, simulando a troca de mensagens entre os processos. A prova fornece uma boa representação passo a passo da execução da rede, servindo para a depuração.

(41)

7

Comparação entre aplicações

utlizando CTJ e Java

Para verificar a complexidade (tando na geração de código quanto na execução) da utiliza-ção do pacote CTJ em aplicações concorrentes, ao invés do modelo de thread utilizado por Java, foi implementado um problema clássico de concorrência, o Jantar dos Filósofos (MAGEE,

2007).

O Problema do Jantar dos Filósofos apresenta cinco (5) filósofos e cinco (5) garfos. A idéia central deste problema é que dado os filósofos e os garfos em um jantar, cada filósofo necessita de dois (2) garfos para comer. Os filósofos podem estar em um destes dois (2) estados: esperando ou comendo. Se um filósofo está no estado esperando e quer passar para o estado comendo, ele tenta pegar dois (2) garfos. Se conseguir pegar os dois garfos ele passa para o estado comendo. Enquanto no estado esperando, o filósofo permanece tentando pegar os garfos, ou seja, fica esperando a liberação dos garfos.

Foram desenvolvidas duas implentações deste problema, uma utilizando as threads de Java (ver apêndice A, B e C) e outra utilizando o pacote CTJ (ver apêndice D, E e F).

(42)

Figura 6: Rede de processos do Jantar dos Filósofos.

Os códigos resultantes são semelhantes, embora a versão CTJ necessite de um pouco mais de lógica para o tratamento das mensagens, tanto no envio quanto no recebimento.

A proteção das regiões criticas na implementação utilizando CTJ é mais simples, pois a sincronização de CTJ é feita pelos canais de comunicação, não existindo a preocupação de quais trechos de código será necessária a inserção da clausula synchronized, diferente de Java, que uma das dificuldades é escolher quais métodos serão sincronizados.

A corrida de hazards é evita em CSP, pois a solicitação de recursos (sejam dados ou propria-mente recursos do sistema, tais como memória, TCP-IP) é feita através dos canais, com o envio de uma mensagem ao processo detentor do recurso. O canal assegura que apenas um processo terá sua solicitação aceita, os demais ficam no aguardo. Já em Java isso deve ser controlado

(43)

pelos métodos wait e notify e pela cláusula synchronized, como já mencionado, é difícil definir quais trechos de código serão acessados simultaneamente por mais de uma thread.

Os deadlocks são evitados, pois a comunicação é feita via canais, e não com o compartil-hamento de trechos de código. Ex.:

Em Java, existem duas regiões críticas e duas threads, sendo que cada thread está de posse de uma região crítica, ver figura 7.

Figura 7: Duas threads tomando posse de regiões críticas distintas.

Porém, dentro das regiões críticas, as threads tentam o acesso a região que está de posse da outra thread, sendo assim ambas serão bloqueadas, gerando um deadlock, como na figura 8.

Figura 8: Deadlock gerado pela tentativa de das thread à regiões críticas bloqueadas.

Em CTJ isso não ocorre, pois as regiões críticas se encontram dentro de um processo, que receberá, através dos canais, as solicitações dos demais processos, não ficando bloqueado permanentemente, porém apenas quando espera que outro processo esteja pronto para efetuar a comunicação. Ex.

Dois processos desejam tomar posse de dois recursos, sendo que cada recurso é controlado por um processo, estes representando as regiões críticas do exemplo anterior. A figura 9 mostra

(44)

a rede de processos deste exemplo.

Figura 9: Rede de processos do exemplo de não ocorrência de deadlock em CTJ.

A figura 10 mostra o Processo1 enviando uma mensagem ao Processo3, que detém um recurso 1, e o Processo2 enviando uma mensagem ao Processo4, que detém o recurso 2.

Figura 10: Processo1 acessando o recurso 1, e o Processo2 acessando o recurso 2.

(45)

assim, podem continuar a se comunicar com os demais processos, mesmo que seja para informar que o recurso desejado não está disponível, como mostra a figura 11.

Figura 11: Processo1 acessando o recurso 2, e o Processo2 acessando o recurso 1, sem a ocor-rência de deadlock.

Porém deadlocks podem ocorrer em CTJ, caso um processo efetue um método de escrita ou leitura, e o processo que deveria efetuar o método correspondente não o faz, sendo o primeiro processo bloqueado para sempre.

A execução do jantar dos filósofos em Java no intervalo de um minuto, gerou uma média de 2058400 acessos aos métodos que são utilizados para o acesso aos dados (são os métodos pegaGarfo()e devolveGarfo() da classe MesaJava, ver apêndice D), já na versão utilizando CTJ, com o mesmo intervalo de tempo, ocorreu em media 6250 leituras no canal de entrada da classe MesaCTJ, ou seja, o número de vezes que a classe MesaCTJ interagiu com um Filósofo.

Essa grande diferença entre o número de interações se deve em parte, ao controle das threads, que CTJ assume completamente, ao invés de deixá-lo a cargo do sistema operacional como Java, afetando seu desempenho. A troca de mensagens também é uma das responsáveis pela queda no desempenho, pois a cada escrita em um canal, o objeto a que se deseja transmitir é copiado, e a sua copia é enviada ao canal.

(46)

8

Conclusão

O pacote Communicating Threads for Java (CTJ) é uma implementação do modelo CSP, resultando em construtores baseados em processos, composições e canais, de uso muito mais simples e mais confiável que o modelo Java. O pacote CTJ fornece um pequeno conjunto de padrões de projeto que é suficiente para programação concorrente em Java. Uma importante vantagem do pacote CTJ é que o programador pode aplicar um conjunto de regras para eliminar os race-hazards, deadlock, livelock, starvation, etc.

A sincronização e o escalonamento dos processos foi muito simplificado com a comuni-cação entre canais e construtores compostos, tornando mais fácil a depuração e o acompan-hamento dos estados do processo.

CTJ possui seu próprio kernel, que faz o escalonamento de suas thread de modo previsível e claro, porém afetando seu desempenho.

O programador não precisar se preocupar com a teoria matemática de CSP, pois é um con-ceito maduro, que possui ferramentas para verificação de erros.

Ao desenvolver aplicações utilizando CSP, a decisão de qual plataforma (plataforma mul-tithread, vários processos rodando em um processador, vários processos sendo executados em diferentes processadores) será utilizada como base para a aplicação pode ser tomada sem muito impacto ao código e ao projeto da aplicação.

Usar CSP como base para o seu programa assegura a separação entre processos, pois a interação entre os mesmo é feita somente através de canais, e não de métodos. Isso também assegura o encapsulamento dos dados e da lógica de cada processo, sem a interação direta de

(47)
(48)

APÊNDICE A -- Classe MesaCTJ

(49)
(50)
(51)

APÊNDICE B -- Classe FilosofoCTJ

(52)

APÊNDICE C -- Classe JantarDosFilosofosMain

Algoritmo 43 Classe JantarDosFilosofos, da aplicação Jantar dos filósofos implementada em CTJ.

(53)

APÊNDICE D -- Classe MesaJava

(54)

APÊNDICE E -- Classe FilosofoJava

(55)

APÊNDICE F -- Classe MainJava

(56)

Referências

HILDERINK, Gerald. Communicating Sequential Processes for JavaTM (JCSP). University of Twente: [s.n.], Setembro 2006. Disponível em: <http://www.ce.utwente.nl/javapp/>. HILDERINK, Gerald; BROENINK, Jan; VERVOORT, Wiek; BAKKERS, Andre. Communicating Java Threads. In: BAKKERS, A. (Ed.). Parallel Programming and Java, Proceedings of WoTUG 20. University of Twente, Netherlands: IOS Press, Netherlands, 1997. v. 50, p. 48–76. Disponível em: <citeseer.ist.psu.edu/hilderink97communicating.html>. HILDERINK, Gerald H. Class Alternative. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Alternative.html>. HILDERINK, Gerald H. Class Channel. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Channel.html>. HILDERINK, Gerald H. Class Guard. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Guard.html>. HILDERINK, Gerald H. Class Parallel. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Parallel.html>. HILDERINK, Gerald H. Class PriAlternative. Setembro 2006. Disponível

em: <http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/PriAlternative.html>.

HILDERINK, Gerald H. Class PriParallel. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/PriParallel.html>. HILDERINK, Gerald H. Class Sequential. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Sequential.html>. HILDERINK, Gerald H. Interface Process. Setembro 2006. Disponível em:

<http://www.cs.bgu.ac.il/ elhadad/advpro/ctj-distribution/ctj-doc/csp/lang/Process.html>. HOARE, C. A. R. Communicating sequential processes. Commun. ACM, ACM Press, New York, NY, USA, v. 26, p. 100–106, 1983. ISSN 0001-0782.

KLEBANOV, Vladimir; RÜMMER, Philipp; SCHLAGER, Steffen; SCHMITT, Peter H. Verification of JCSP programs. In: BROENINK, J.F.; ROEBBERS, H.W.; SUNTER, J.P.E.; WELCH, P.H.; WOOD, D.C. (Ed.). Communicating Process Architectures 2005. IOS Press, The Netherlands: IOS Press, 2005. (Concurrent Systems Engineering Series, v. 63), p. 203–218. ISBN 1-58603-561-4. ISSN 1383-7575. Disponível em: <http://www.cs.chalmers.se/ philipp/publications/jcsp2005.pdf>.

(57)

MAGEE, Jeff. Jantar dos Filósofos (Dining Philosophers). Fevereiro 2007. Disponível em: <http://www.doc.ic.ac.uk/ jnm/concurrency/classes/Diners/Diners.html>.

MARTIN, J. M. R.; JASSIM, S. A. A Tool for Proving Deadlock Freedom. In: BAKKERS, A. (Ed.). Parallel Programming and Java, Proceedings of WoTUG 20. University of Twente, Netherlands: IOS Press, Netherlands, 1997. v. 50, p. 1–16.

MARTIN, J. M. R.; JASSIM, S. A. The Deadlock Checker Tool. The University of Kent: [s.n.], Janeiro 2007. Disponível em: <http://wotug.kent.ac.uk/parallel/theory/formal/csp/Deadlock/>. P.D.AUSTIN. Interface Channel. Setembro 2006. Disponível em:

<http://www.cs.kent.ac.uk/projects/ofa/jcsp/jcsp1-0-rc7/jcsp-docs/jcsp/lang/Channel.html>. SUN. Class Object. Setembro 2006. Disponível em:

<http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html>. SUN. Class Runnable. Setembro 2006. Disponível em: <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Runnable.html>. SUN. Class Thread. Setembro 2006. Disponível em:

<http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.html>. SUN. Enum Thread.State. Setembro 2006. Disponível em: <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.State.html>. SUN. Threading. Dezembro 2006. Disponível em:

<http://java.sun.com/docs/hotspot/threads/threads.html>.

SUN. Java Card Technology. Janeiro 2007. Disponível em: <http://java.sun.com/products/javacard/>.

WELCH, Peter. Communicating Sequential Processes for JavaTM (JCSP). UNI-VERSITY OF KENT At Canterbury: [s.n.], Setembro 2006. Disponível em: <http://www.cs.kent.ac.uk/projects/ofa/jcsp/>.

Referências

Documentos relacionados

o cartão de respostas para cada aluno, contendo seu nome e suas respostas. 5) Crie um vetor capaz de armazenar 50 números inteiros. Em seguida faça o seu preenchimento automático com

6) Faça um programa para cadastrar o horário de partida de vôos, com os dados: código, horário de partida, quantidade de passageiros, valor da passagem, origem e destino.

Os dados de perda de água e sedimento foram obtidos em duas parcelas de erosão de 100 m² com sistema de manejo e cobertura superfi cial do solo diferenciada, sendo uma parcela

Sua obra mostrou, ainda, que civilização e exploração do trabalho andam juntas e que o avanço histórico do oeste brasileiro se fez com a carne e o sangue dos

Sendo assim, o Capítulo 6 introduz o detalhamento do algoritmo FOntGAR ( Fuzzy Ontology-based Generalized Association Rules Algorithm ), um novo algoritmo

Para entendimento desse trabalho é importante explanar conceitos relacionados as áreas que o abrangem como: Building Information Modeling (BIM), Linguagem IFC,

Em 2015, a propriedade “A” também teve melhor resultado do que o amealhado na propriedade “B”, tanto na produção média por matriz, quanto no custo médio do litro do leite..

portanto, a não consideração da contribuição patrocinadora para pensionistas nos presente estudo, no que se refere aos valores aqui apresentados (contribuições, custeio