Upload
dangduong
View
230
Download
0
Embed Size (px)
Citation preview
João Vitor Mallmann
Programação Concorrente Segura em Java
Trabalho de conclusão de curso apresentadocomo parte dos requisitos para obtenção do graude Bacharel em Ciências da Computação
Orientador:
José Mazzucco Júnior
BACHARELADO EM CIÊNCIAS DA COMPUTAÇÃO
DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA
CENTRO TECNOLÓGICO
UNIVERSIDADE FEDERAL DE SANTA CATARINA
Florianópolis
Fevereiro de 2007
Resumo
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 threadpara 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 desempenhosdiferentes ao utilizar sistemas operacionais distintos. A liberdade da programação concorrenteem Java aumenta o perigo de hazards, deadlock, livelock e starvation, problemas comuns aaplicaçõ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 teoriamatemática para descrição de computação concorrente e distribuída, que garante a ausência dosproblemas 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 bibliotecaCTJ.
Lista de Algoritmos
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
22 Construtor de composição Alternativo. . . . . . . . . . . . . . . . . . . . . . p. 32
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
em CTJ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 51
44 Classe Mesa, da aplicação Jantar dos filósofos implementada em Java. . . . . p. 52
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
Sumário
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
4.1 Independente de hardware: . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 23
4.2 Dependente de hardware: . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 23
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
Apêndice C -- Classe JantarDosFilosofosMain p. 51
Apêndice D -- Classe MesaJava p. 52
Apêndice E -- Classe FilosofoJava p. 53
Apêndice F -- Classe MainJava p. 54
Referências p. 55
Lista de Figuras
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
ocorrência de deadlock. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 44
10
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
11
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.
12
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.
• Desenvolvimento de aplicações utilizando o modelo de CSP para Java, para comparações
13
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.
14
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 Profes-
15
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.
16
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
(que nada mais é que a construção de um monitor) protege a região crítica (trecho de código
17
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 sincronizados. 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.
18
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.
Figura 1: Interface de processo (HILDERINK et al., 1997).
19
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.
20
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:
21
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.
22
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.
23
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 processos
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 portabil-
24
idade das aplicações, pois caso haja a necessidade 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.
25
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
26
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().
O algoritmo 5 na página seguinte mostra uma composição seqüencial de três processos.
27
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
Processo3 apó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.
28
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.
Da mesma forma, processos podem ser removidos:
29
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 construtor
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).
O exemplo 16 mostra uma composição paralela de três processos com prioridade:
30
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.
31
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):
32
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
33
í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
run().
34
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.
Algoritmo 31 Adição de vários guardas a composição PriAlternative.
35
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).
36
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
37
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.
A condição de execução do guarda pode ser modificada pelo método setEnabled().
38
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
39
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.
40
7 Comparação entre aplicaçõesutlizando 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).
A rede de processos dessa aplicação está representada na figura 9.
41
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
42
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
43
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.
Os processos que controlam os recursos, não serão bloqueados permanentemente, sendo
44
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.
45
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
APÊNDICE A -- Classe MesaCTJ
Algoritmo 39 Parte 1 da classe Mesa, da aplicação Jantar dos filósofos implementada em CTJ.
50
APÊNDICE B -- Classe FilosofoCTJ
Algoritmo 42 Classe Filosofo, da aplicação Jantar dos filósofos implementada em CTJ.
51
APÊNDICE C -- Classe JantarDosFilosofosMain
Algoritmo 43 Classe JantarDosFilosofos, da aplicação Jantar dos filósofos implementada em
CTJ.
52
APÊNDICE D -- Classe MesaJava
Algoritmo 44 Classe Mesa, da aplicação Jantar dos filósofos implementada em Java.
53
APÊNDICE E -- Classe FilosofoJava
Algoritmo 45 Classe Filosofo, da aplicação Jantar dos filósofos implementada em Java.
54
APÊNDICE F -- Classe MainJava
Algoritmo 46 Classe Main, da aplicação Jantar dos filósofos implementada em Java.
55
Referências
HILDERINK, Gerald. Communicating Sequential Processes for JavaTM (JCSP). Universityof 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ívelem: <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, NewYork, 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 Architectures2005. IOS Press, The Netherlands: IOS Press, 2005. (Concurrent Systems EngineeringSeries, 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>.
56
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/>.