175
UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO PROGRAMAÇÃO CONCORRENTE – 2015.1 Fábio M. Pereira ([email protected])

Programação Concorrente - Objetos e Concorrência

Embed Size (px)

Citation preview

Page 1: Programação Concorrente - Objetos e Concorrência

UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO

PROGRAMAÇÃO CONCORRENTE – 2015.1

Fábio M. Pereira

([email protected])

Page 2: Programação Concorrente - Objetos e Concorrência

Roteiro

• Concorrência • Objetos e Concorrência

– Construtores de Execução Concorrente – Concorrência e Programação Orientada a Objetos – Modelos de Objetos e Mapeamento

• Forças de Projeto – Segurança – Vivacidade – Performance – Reusabilidade

• Padrões de Projeto – Aplicação de Camadas – Adaptadores – Subclasses – Adaptadores de Método

Page 3: Programação Concorrente - Objetos e Concorrência
Page 4: Programação Concorrente - Objetos e Concorrência

Concorrência

• Informalmente, um programa concorrente é aquele que faz mais de uma coisa ao mesmo tempo

• Por exemplo, um navegador web pode simultaneamente realizar uma requisição HTTP GET para buscar uma página, executar um clipe de áudio, mostrar a quantidade de bytes recebidos de alguma imagem e realizar um diálogo de advertência com o um usuário

• Mas às vezes esta simultaneidade é uma ilusão – Em alguns sistemas computacionais estas atividades diferentes

podem ser realizadas por diferentes CPUs

– Em outros sistemas elas são realizadas por uma única CPU compartilhada que troca entre diferentes atividades tão rápido que elas parecem serem simultâneas para um observador humano

Page 5: Programação Concorrente - Objetos e Concorrência

Concorrência

• Uma definição de programação concorrente mais precisa, porém não muito interessante, pode ser feita operacionalmente: – Uma máquina virtual Java e o sistema operacional fornecem

mapeamentos a partir de simultaneidade aparente para paralelismo físico (via múltiplas CPUs) ou, na falta deste, por permitir que atividades independentes sejam realizadas em paralelo quando possível ou desejável, e em outro caso, por compartilhamento de tempo

– Programação concorrente consiste no uso de construtores de programação que são mapeados desta maneira

– Por convenção, esta noção está restrita a construtores afetando uma única JVM, como oposto à programação distribuída, que, por exemplo, usando remote method invocation (RMI), envolve múltiplas JVMs residindo em múltiplos sistemas computacionais

Page 6: Programação Concorrente - Objetos e Concorrência

Concorrência

• Concorrência e os motivos para seu uso são melhor capturados quando consideramos a natureza de alguns tipos comuns de aplicações concorrentes:

– Web services: a maioria dos web services baseados em sockets (por exemplo, daemons HTTP, máquinas servlet e servidores de aplicação) são multithreaded, normalmente, a principal motivação e dar suporte a múltiplas conexões concorrentes para garantir que uma nova solicitação de conexão não precise aguardar a finalização de outras, o que geralmente minimiza a latência e aumenta a disponibilidade

– Cálculos numéricos: muitas tarefas intensivas de computação podem ser paralelizadas, e então serem executadas mais rápido se múltiplas CPUs estão presentes, aqui o objetivo é maximizar a saída explorando o paralelismo

Page 7: Programação Concorrente - Objetos e Concorrência

Concorrência

• Continuação...

– Processamento I/O: mesmo em um computador nominalmente sequencial, dispositivos que realizam leituras e escritas em discos, redes, etc., operam independentemente da CPU, programas concorrentes podem usar o tempo que outros programas perdem esperando por I/O lento, e podem então fazer uso mais eficiente do recursos de um computador

– Simulação: programas concorrentes podem simular objetos físicos com comportamento autônomo independente que são difíceis de realizar em programas puramente sequenciais

Page 8: Programação Concorrente - Objetos e Concorrência

Concorrência

• Continuação...

– Aplicações baseadas em GUI: mesmo que a maioria das interfaces do usuário sejam intencionalmente single-threaded, elas geralmente estabelecem ou se comunicam com serviços multithreaded, a concorrência permite que o controle do usuário continue “responsivo” mesmo durante ações que consomem muito tempo

– Software baseado em componentes: componentes de software de alta granularidade (por exemplo, aqueles que fornecem ferramentas de projeto, como editores de layout) pode internamente construir threads de maneira a assistir no registro, fornecer suporte multimídia, alcançar maior autonomia ou melhorar a performance

Page 9: Programação Concorrente - Objetos e Concorrência

Concorrência

• Continuação... – Código móvel: frameworks como o pacote java.applet executam

código baixado em threads separadas como uma parte de um conjunto de políticas que ajudam a isolar, monitorar e controlar os efeitos de código desconhecido

– Sistemas embarcados: a maioria de programas executados em pequenos dispositivos dedicados realizam controle de tempo real, múltiplos componentes estão continuamente reagindo a entradas externas de sensores e outros dispositivos e produzem saídas externas de uma maneira ininterrupta. Todas as implementações JVM dão suporte a controle de tempo real leve, no qual linhas de tempo e performance são considerados tópicos de qualidade de serviço em vez de corretude. Isto reflete objetivos de portabilidade que permitem que a JVM seja implementada em modernos sistemas de hardware e software de multipropósitos

Page 10: Programação Concorrente - Objetos e Concorrência
Page 11: Programação Concorrente - Objetos e Concorrência

Construtores de Execução Concorrente

• Threads são apenas um dos vários construtores disponíveis para execução de código concorrente

• A ideia de gerar uma nova atividade pode ser mapeada para qualquer uma de várias abstrações que refletem trade-offs de autonomia versus overhead

• Projetos baseados em threads nem sempre fornecem a melhor solução para um determinado problema de concorrência

• A escolha de uma das alternativas apresentadas a seguir podem fornecer mais ou menos segurança, proteção, tolerância a falhas e controle administrativo, assim como maior ou menor overhead

• Diferenças entre estas opções (e seus construtores de programação de suporte associados) impactam em diferentes estratégias de projeto

Page 12: Programação Concorrente - Objetos e Concorrência

Sistemas Computacionais

• Se tivéssemos uma grande oferta de sistemas computacionais, poderíamos mapear cada unidade lógica de execução para um computador diferente

• Cada sistema computacional pode ter um único processador, um multiprocessador, ou um conjunto de máquinas administradas como uma única unidade e compartilhando um sistema operacional comum:

– Isso proporciona autonomia ilimitada e independência

– Cada sistema pode ser administrado separadamente e controlado a partir de todos os outros

Page 13: Programação Concorrente - Objetos e Concorrência

Sistemas Computacionais

• No entanto, a construção, localização, recuperação, e passagem de mensagens entre tais entidades podem ser caros, as oportunidades para compartilhamento de recursos locais são eliminados, e soluções para os problemas em torno de nomes, segurança, tolerância a falhas, recuperação e acessibilidade são todos relativamente pesados em comparação com os observados em programas concorrentes

• Portanto, esta escolha de mapeamento é tipicamente aplicado apenas em relação aos aspectos de um sistema que exige uma solução intrinsecamente distribuída

• E mesmo dispositivos embarcados de computador ínfimos hospedam mais de um processo

Page 14: Programação Concorrente - Objetos e Concorrência

Processos

• Um processo é uma abstração do sistema operacional que permite que um sistema computacional dê suporte a muitas unidades de execução

• Cada processo representa tipicamente um programa em execução em separado; por exemplo, uma execução de JVM

• Como a noção de um "sistema computacional", um "processo" é uma abstração lógica, não física, assim, por exemplo, ligações de processos para CPUs podem variar dinamicamente

Page 15: Programação Concorrente - Objetos e Concorrência

Processos

• Os sistemas operacionais garantem algum grau de independência, a ausência de interferências e segurança entre os processos executados concorrentemente

• Processos em geral, não têm permissão para acessar locais de armazenamento uns dos outros (embora geralmente existam algumas exceções), e devem, em vez disso, se comunicar via recursos de comunicação entre processos, tais como pipes (tubos)

• A maioria dos sistemas fazem pelo menos promessas de melhor esforço sobre como processos serão criados e agendados

• Isso quase sempre implica em compartilhamento de tempo preemptivo - a suspensão de processos em uma base periódica para dar a outros processos a chance de executar

Page 16: Programação Concorrente - Objetos e Concorrência

Processos

• A sobrecarga para a criação, gestão e comunicação entre processos pode ser muito menor do que em soluções por máquina

• No entanto, uma vez que os processos compartilham recursos computacionais subjacentes (CPUs, memória, canais de E/S), eles são menos autônomos

– Por exemplo, um travamento de máquina causado por um processo mata todos os processos

Page 17: Programação Concorrente - Objetos e Concorrência

Threads

• Construtores de threads de várias formas fazem mais trade-offs em autonomia, em parte por causa da menor sobrecarga

• Os principais trade-offs são:

– Compartilhamento

– Agendamento

– Comunicação

Page 18: Programação Concorrente - Objetos e Concorrência

Threads

call

call

task

task

thread

call

call task thread

process

system call

call

call

call

task

task

thread process

call

call

task

task

thread

thread

process

Page 19: Programação Concorrente - Objetos e Concorrência

Compartilhamento

• Threads podem compartilhar o acesso à memória, arquivos abertos e outros recursos associados a um único processo

• Threads na linguagem de programação Java podem compartilhar todos esses recursos

• Alguns sistemas operacionais também suportam construções intermediárias, por exemplo "processos leves" e "threads do kernel" que compartilham apenas alguns recursos, fazendo-o apenas mediante pedido explícito, ou impondo outras restrições

Page 20: Programação Concorrente - Objetos e Concorrência

Agendamento

• Garantia de independência pode ser enfraquecida para apoiar políticas de escalonamento mais baratas

• Em um extremo, todas as threads podem ser tratadas em conjunto, como um processo single-threaded, caso em que elas podem lidar de forma cooperativa com as outras, de modo que apenas uma thread está sendo executada de cada vez, sem dar a qualquer outra thread a chance de executar até que ela bloqueie

• No outro extremo, o agendador subjacente pode permitir que todas as threads em um sistema lidem diretamente umas com as outras através de regras de agendamento preemptivas

• Threads na linguagem de programação Java podem ser agendadas usando qualquer política que encontra-se em qualquer lugar entre estes dois extremos

Page 21: Programação Concorrente - Objetos e Concorrência

Comunicação

• Sistemas interagem via comunicação através de canais com ou sem fios, por exemplo, utilizando sockets

• Processos também podem se comunicar dessa forma, mas também podem utilizar mecanismos mais leves, como pipes (tubos) e recursos de sinalização entre processos

• Threads podem usar todas essas opções, além de outras estratégias mais baratas que dependem de acesso a posições de memória acessíveis por múltiplas threads, e empregando recursos de sincronização baseadas em memória, como bloqueios, espera e os mecanismos de notificação

• Estes construtores apoiam a comunicação de maneira mais eficiente, mas algumas vezes incorrem em maior complexidade e, consequentemente, maior potencial de erro de programação

Page 22: Programação Concorrente - Objetos e Concorrência

Tarefas e Ambientes Leves de Execução

• Os trade-offs efetuados em suporte a threads cobrem uma ampla gama de aplicações, mas que nem sempre são perfeitamente compatíveis com as necessidades de uma determinada atividade

• Embora os detalhes de desempenho diferem em todas as plataformas, a sobrecarga na criação de uma thread ainda é significativamente maior do que a forma mais barata (mas menos independente) para invocar um bloco de código – chamando-o diretamente na thread atual

Page 23: Programação Concorrente - Objetos e Concorrência

Tarefas e Ambientes Leves de Execução

• Quando a sobrecarga de criação e gerenciamento de threads tornam-se preocupações de desempenho, podemos ser capazes de fazer concessões adicionais de autonomia pela criação de nossos próprios ambientes de execução mais leves, que impõem novas restrições à sua utilização (por exemplo, proibindo o uso de certas formas de bloqueio), ou fazendo menos garantias de agendamento, ou restringindo a sincronização e comunicação a um conjunto mais limitado de opções

Page 24: Programação Concorrente - Objetos e Concorrência

Tarefas e Ambientes Leves de Execução

• Os ambientes leves de execução mais familiares são sistemas baseados em eventos e subsistemas, nos quais as chamadas disparadas de atividades conceitualmente assíncronas são mantidas como eventos que podem ser enfileirados e processados por threads em background

• Quando se aplicam, a construção e utilização de tais ambientes podem melhorar tanto a estrutura como o desempenho de programas concorrentes

• O seu uso reduz as preocupações que podem de outro modo inibir o uso de técnicas de execução concorrente para expressar atividades logicamente assíncronas e objetos logicamente autônomos

Page 25: Programação Concorrente - Objetos e Concorrência
Page 26: Programação Concorrente - Objetos e Concorrência

Concorrência e Programação OO

• Objetos e concorrência estão vinculados, desde os primeiros dias de cada um

• A primeira linguagem de programação concorrente OO (criada em cerca de 1966), Simula, também foi a primeira linguagem OO, e foi uma das primeiras linguagens concorrentes

• Os construtores iniciais de OO e concorrência de Simula eram um tanto primitivos e estranhos

– Por exemplo, a concorrência foi baseado em torno de co-rotinas – construtores baseados em thread que exigiam que programadores explicitamente passassem o controle de uma tarefa para outra

Page 27: Programação Concorrente - Objetos e Concorrência

Concorrência e Programação OO

• Várias outras linguagens que oferecem ambos os construtores, de concorrência e OO, se seguiram – na verdade, até mesmo algumas das versões dos primeiros protótipos de C++ incluíam algumas classes de bibliotecas com suporte à concorrência

• E Ada (embora, em suas primeiras versões, dificilmente se parecia com uma linguagem OO) ajudou a trazer programação concorrente para fora do mundo dos sistemas e linguagens de baixo nível especializados

Page 28: Programação Concorrente - Objetos e Concorrência

Concorrência e Programação OO

• Projeto OO não desempenhou nenhum papel prático nos sistemas multithreaded de programação emergentes na década de 1970

• E a concorrência não desempenhou nenhum papel prático na programação OO de larga escala que começou na década de 1980

• Mas o interesse em OO concorrente permaneceu vivo em laboratórios de pesquisa e grupos de desenvolvimento avançados, e ressurgiu como um aspecto essencial da programação, em parte devido à popularidade e onipresença da plataforma Java

• Programação OO concorrente compartilha a maioria das características da programação de qualquer tipo, mas difere em aspectos críticos dos tipos de programação que podemos estar mais familiarizados

Page 29: Programação Concorrente - Objetos e Concorrência

Programação OO Sequencial

• Programas OO concorrentes são muitas vezes estruturados utilizando as mesmas técnicas de programação e padrões de projeto como programas OO sequenciais

• Mas eles são intrinsecamente mais complexos – quando mais do que uma atividade pode ocorrer ao mesmo tempo, a execução do programa é, necessariamente, não-determinística

• O código pode executar em ordem surpreendente – qualquer fim que não seja explicitamente excluído é permitido, então não podemos sempre entender programas concorrentes por leitura sequencial através de seu código

Page 30: Programação Concorrente - Objetos e Concorrência

Programação OO Sequencial

• Por exemplo, sem maiores precauções, um campo definido para um valor em uma linha de código pode ter um valor diferente (devido às ações de alguma outra atividade simultânea) pelo tempo em que a próxima linha de código é executada

• Lidar com essa e outras formas de interferência muitas vezes introduz a necessidade de um pouco mais de rigor e uma perspectiva mais conservadora no design

Page 31: Programação Concorrente - Objetos e Concorrência

Programação Baseada em Eventos

• Algumas técnicas de programação concorrente têm muito em comum com as de ambientes de eventos empregadas em kits de ferramentas GUI como o java.awt, javax.swing e em outras linguagens, como Tcl/Tk e Visual Basic

• Em ambientes de GUI, eventos como cliques do mouse são encapsulados como objetos Event que são colocados em um único EventQueue

• Estes eventos são depois enviados e processados um a um em um único ciclo de eventos, que normalmente é executado como uma thread separada

Page 32: Programação Concorrente - Objetos e Concorrência

Programação Baseada em Eventos

• Este projeto pode ser estendido para dar suporte a concorrência adicional por (entre outras táticas) a criação de várias threads de ciclo de eventos, cada evento processamento concorrentemente, ou até mesmo disparando cada evento em uma thread separada

• Novamente, isso abre novas possibilidades de projeto, mas também introduz novas preocupações sobre interferência e coordenação entre as atividades concorrentes

Page 33: Programação Concorrente - Objetos e Concorrência

Programação de Sistemas Concorrentes

• Programação concorrente orientada a objetos difere de programação de sistemas multithreaded em linguagens como C, devido, principalmente, ao encapsulamento, modularidade, extensibilidade, segurança e recursos de segurança que faltam em C

• Além disso, suporte a concorrência é construído na linguagem de programação Java, e não fornecido por bibliotecas

• Isso elimina a possibilidade de alguns erros comuns, e também permite que os compiladores automaticamente e com segurança executem algumas otimizações que precisam ser executadas manualmente em C

Page 34: Programação Concorrente - Objetos e Concorrência

Outras Linguagens de Programação Concorrente

• Essencialmente, todas as linguagens de programação concorrente são, em algum nível, equivalentes, se considerarmos apenas o sentido de todas as linguagens concorrentes, acreditamos não termos definido as características de concorrência certas

• No entanto, não é tão difícil fazer programas em uma linguagem parecerem quase equivalentes aos de outras linguagens ou aquelas que utilizam outros construtores, através do desenvolvimento de pacotes, classes, utilitários, ferramentas e convenções de codificação que imitam recursos incorporados em outras

Page 35: Programação Concorrente - Objetos e Concorrência
Page 36: Programação Concorrente - Objetos e Concorrência

Modelos de Objetos e Mapeamentos

• Concepções de objetos muitas vezes diferem entre programação OO sequencial e concorrente, e mesmo entre diferentes estilos de programação OO concorrente

• Contemplação dos modelos de objetos e mapeamentos subjacentes pode revelar a natureza das diferenças entre os estilos de programação sugeridos na seção anterior

• A maioria das pessoas gosta de pensar em objetos de software como modelos de objetos reais, representados com algum grau arbitrário de precisão

• A noção de "real" é, naturalmente, aos olhos de quem vê, e muitas vezes inclui artifícios que só fazem sentido dentro do reino da computação

Page 37: Programação Concorrente - Objetos e Concorrência

Exemplo

• Considerando o diagrama de classes UML e o esboço de código para a classe WaterTank:

class Watertank { // esboço de código

final float capacity;

float currentVolume = 0.0f;

WaterTank overflow;

WaterTank(float cap) { capacity = cap; ... }

void addWater(float amount)

throws OverflowException;

void removeWater(float amount)

throws UnderflowException;

}

Page 38: Programação Concorrente - Objetos e Concorrência

Exemplo

• A intenção aqui é representar, ou simular, um tanque de água com:

– Atributos, tais como capacity e currentVolume, que são representados como campos de objetos Watertank

– Podemos escolher somente os atributos que nos preocupemos em algum contexto de uso, por exemplo, enquanto todos os tanques de água reais têm locais, formas, cores e assim por diante, esta classe só lida com volumes

– Restrições de estado invariantes, como os fatos de que o currentVolume permanece sempre entre zero e capacity, e que capacity não seja negativo e nunca mude após a construção

Page 39: Programação Concorrente - Objetos e Concorrência

Exemplo

– Operações que descrevem comportamentos, como aqueles para addWater e removeWater

– Esta escolha de operações reflete novamente algumas decisões de design implícitas relativas a exatidão, granularidade e precisão

– Por exemplo, poderíamos ter escolhido para modelar os reservatórios de água ao nível das válvulas e interruptores, e poderíamos ter modelado cada molécula de água como um objeto que muda de localização como o resultado das operações associadas

Page 40: Programação Concorrente - Objetos e Concorrência

Exemplo

– Conexões (e conexões potenciais) para outros objetos com os quais objetos se comunicam, tais como tubos ou outros tanques

– Por exemplo, o excesso de água encontrado numa operação addWater pode ser desviado para um tanque de transbordamento que é conhecido por cada tanque

– Pré-condições e pós-condições sobre os efeitos das operações, como as regras segundo as quais é impossível remover a água de um tanque vazio, ou adicionar água para um tanque cheio que não esteja equipado com um tanque de transbordamento disponível

– Restrições de Protocolos quando e como mensagens (os pedidos de operação) são processados

– Por exemplo, podemos impor uma regra de que no máximo uma mensagem de addWater ou removeWater é processada em um determinado momento ou, em alternativa, uma regra afirmando que as mensagens removeWater são permitidos no meio de operações addWater

Page 41: Programação Concorrente - Objetos e Concorrência

Modelos de Objetos

• A classe Watertank usa objetos para modelar a realidade

• Os modelos de objetos fornecem regras e ambientes para a definição de objetos de forma mais geral, que abrange:

– Estado – a estrutura de cada objeto é descrito (normalmente através de uma classe), em termos de atributos internos (estado), conexões com outros objetos, métodos locais (internos), e métodos ou portas para aceitar mensagens de outros objetos

– Encapsulamento - os objetos têm membranas que separam o

seu interior do exterior, o estado interno pode ser diretamente

modificado apenas pelo próprio objeto

Page 42: Programação Concorrente - Objetos e Concorrência

Modelos de Objetos

– Comunicação - objetos se comunicam apenas através de passagem de mensagem, mensagens de objetos disparam ações em outros objetos

– As formas dessas mensagens podem variar de chamadas de procedimentos simples até aqueles transportados via protocolos arbitrários de comunicação

– Identidade - novos objetos podem ser construídos em qualquer momento (sujeito a restrições de recursos do sistema) por qualquer objeto (sujeito a controle de acesso), uma vez construído, cada objeto mantém uma identidade única que persiste ao longo de sua vida

Page 43: Programação Concorrente - Objetos e Concorrência

Modelos de Objetos

– Conexões – um objeto pode enviar mensagens para os outros, se ele conhece suas identidades

– Alguns modelos contam com identidades de canal em vez de ou em adição a identidades de objetos

– Abstratamente, um canal é um veículo para a passagem de mensagens

– Dois objetos que compartilham um canal podem passar mensagens através desse canal, sem saber as identidades de cada um

– Linguagens e modelos OO típicos se baseiam em primitivas de objeto para chamada direta de métodos, abstrações baseadas em canal para ES e comunicação através de rede, e construções, tais como canais de eventos que podem ser vistos a partir de qualquer perspectiva

Page 44: Programação Concorrente - Objetos e Concorrência

Modelos de Objetos

– Computação - objetos podem realizar quatro tipos básicos de computação: • Aceitar uma mensagem

• Atualizar estado interno

• Enviar uma mensagem

• Criar um novo objeto

• Esta caracterização abstrata pode ser interpretada e refinada de várias maneiras: – Por exemplo, uma forma de implementar um objeto Watertank é a construção de um pequeno dispositivo de hardware de propósito especial que só mantém os estados indicados, instruções e conexões

– Mas desde que este não é um curso sobre design de hardware, vamos ignorar tais opções e restringir a atenção para alternativas baseadas em software

Page 45: Programação Concorrente - Objetos e Concorrência

Mapeamento Sequencial

• As características de um computador comum de uso geral (CPU, um canal de comunicação, alguma memória, e algumas portas IO) podem ser exploradas de modo a que este computador pode fingir que é qualquer objeto, por exemplo, um Watertank

• Isso pode ser realizado ao carregar uma descrição do reservatórios de água (através de um arquivo .class) em uma JVM

• A JVM pode, então, construir uma representação passiva de uma instância e, em seguida, interpretar as operações associadas

• Esta estratégia de mapeamento também se aplica ao nível da CPU quando as operações são compilados em código nativo em vez de interpretadas como bytecodes

• Ela também se estende aos programas que incluam muitos objetos de classes diferentes, cada um carregado e instanciado, conforme necessário, fazendo com que a JVM em todos os momentos registre a identidade ("this") do objeto que está simulando

Page 46: Programação Concorrente - Objetos e Concorrência

Mapeamento Sequencial

JVM Sequencial

Interpretador

interpret() {

...

}

IO

Representação de um objeto

WaterTank Representação de uma classe

WaterTank

Estado: contador do programa, endereços de objetos

Page 47: Programação Concorrente - Objetos e Concorrência

Mapeamento Sequencial

• Em outras palavras, a JVM é em si um objeto, embora um muito especial que pode fingir que é outro objeto (mais formalmente, ela serve como uma Máquina Universal de Turing)

• Enquanto estados semelhantes valem para os mapeamentos utilizados na maioria das outras linguagens, objetos Class e reflexão tornam mais simples caracterizar objetos reflexivos que tratam outros objetos como dados

• Em um ambiente puramente sequencial, isto seria o fim da história

Page 48: Programação Concorrente - Objetos e Concorrência

Mapeamento Sequencial

• Mas considerando as restrições sobre o modelo de objetos genérico imposto por este mapeamento, em uma JVM sequencial seria impossível simular diretamente múltiplos objetos waterTank interagindo concorrentemente

• E uma vez que toda passagem de mensagem é realizada via invocação de procedimento sequencial, não há necessidade de regras sobre se várias mensagens podem ser processadas concorrentemente

• Assim, o processamento OO sequencial limita os tipos de conceitos de projeto de alto nível que são permitidos expressar

Page 49: Programação Concorrente - Objetos e Concorrência

Objetos Ativos

• No outro extremo do espectro de mapeamento estão modelos de objetos ativos (também conhecidos como modelos de ator), em que cada objeto é autônomo

• Cada um pode ser tão poderoso como uma JVM sequencial

• Classes internas e representações de objetos podem ter as mesmas formas como as usadas em estruturas passivas

– Por exemplo, aqui, cada waterTank poderia ser mapeado para um objeto ativo separado, pelo carregamento em uma descrição de uma JVM separada, e, em seguida, sempre permitindo simular as ações definidas

Page 50: Programação Concorrente - Objetos e Concorrência

Objetos Ativos

• Modelos de objeto ativos formam uma visão de alto nível comum de objetos em sistemas orientados a objetos distribuídos: diferentes objetos podem residir em máquinas diferentes, por isso o local e domínio administrativo de um objeto são muitas vezes questões importantes de programação

• Toda a passagem de mensagens é organizada através de comunicação remota (por exemplo, através de sockets) que podem obedecer a qualquer de uma série de protocolos, incluindo: – mensagens de sentido único (mensagens que não intrinsecamente

requerem respostas)

– multicasts (envio simultaneamente da mesma mensagem para vários destinatários) e

– estilo procedimental de trocas de solicitação-resposta

Page 51: Programação Concorrente - Objetos e Concorrência

Objetos Ativos

• Este modelo também serve como uma visão orientada a objetos da maioria dos processos de nível do sistema operacional, cada um dos quais é tão independente de (e compartilham poucos recursos com) outros processos quanto possível

Page 52: Programação Concorrente - Objetos e Concorrência

Objetos Ativos

um objeto ativo

atributos, conexões

accept message

anAction() {

update state

send messages

create objects

}

mensagem

Page 53: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Os modelos e mapeamentos subjacentes dão suporte a concorrência na linguagem de programação Java e está entre os dois extremos: dos modelos passivos e ativos

• Uma JVM completa pode ser composta de vários threads, cada uma das quais atua da mesma maneira que uma única JVM sequencial, no entanto, ao contrário dos objetos ativos puros, todos essas threads podem compartilhar o acesso ao mesmo conjunto de representações passivas subjacentes

• Este estilo de mapeamento pode simular cada um dos extremos

Page 54: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Modelos puramente sequenciais passivos podem ser programados usando apenas uma única thread

• Modelos puramente ativos podem ser programados através da criação de tantas threads quanto objetos ativos existentes, evitando situações em que mais de uma thread possa acessar uma determinada representação passiva, e usando construtores que fornecem os mesmos efeitos semânticos que passagem de mensagens remotas, no entanto, a maioria dos programas concorrentes ocupam uma posição intermediária

Page 55: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Modelos OO concorrentes baseados em threads conceitualmente separam objetos passivos "normais" de objetos ativos (threads)

• Mas os objetos passivos geralmente exibem consciência-thread não vista na programação sequencial, por exemplo, pela proteção através de bloqueios

• E os objetos ativos são mais simples do que os observados em modelos de ator, dando suporte a apenas algumas operações (como run)

Page 56: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Mas o projeto de sistemas OO concorrentes pode ser abordado a partir de qualquer uma destas duas direções:

– por objetos passivos conscientizados de existirem em um ambiente multithread,

– ou por “emburrecimento” de objetos ativos para que eles possam ser expressos mais facilmente usando construtores de thread

Page 57: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

uma Thread

run() {

...

}

estado da thread

uma Thread

estado da thread

run() {

...

}

uma Thread

estado da thread

run() {

...

}

Representação de um objeto

WaterTank Representação de uma classe

WaterTank

Page 58: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Uma das razões para apoiar este tipo de modelo de objetos é que ele mapeia de forma simples e eficiente o armazenamento em hardware e sistemas operacionais em processador único e em multi-processamento de memória compartilhada (SMP):

– threads podem ser vinculadas a CPUs quando possível e desejável, e de outra forma a tempo-compartilhado

– o estado de threads locais são mapeadas para registradores e CPUs

– e representações de objetos compartilhados mapeados para a memória principal compartilhada

Page 59: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

CPU

cache de registradores

CPU

cache de registradores

bus

IO

memória principal

uma representação de objeto

Page 60: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• O grau de controle do programador sobre esses mapeamentos é uma distinção que separa muitas formas de programação paralela de programação concorrente

• Programação paralela clássica envolve etapas de projeto explícito para mapear threads, tarefas ou processos, bem como de dados, para processadores físicos e seus locais de armazenamento

• Programação concorrente deixa a maioria das decisões de mapeamento para a JVM (e o sistema operacional subjacente), isto melhora a portabilidade, às custas da necessidade de acomodar as diferenças na qualidade de implementação destes mapeamentos

Page 61: Programação Concorrente - Objetos e Concorrência

Modelos Mistos

• Compartilhamento de tempo é realizado mediante a aplicação do mesmo tipo de estratégia de mapeamento para as próprias threads: representações de objetos Thread são mantidas, e um agendador organiza trocas de contexto em que o estado da CPU correspondente a uma thread é guardado em sua representação de armazenamento associada e restaurado a partir de outra

• Vários refinamentos e extensões de tais modelos e mapeamentos são possíveis – Por exemplo, as aplicações e os sistemas de

objetos persistentes normalmente contam com bases de dados para manter representações de objetos em vez de depender diretamente na memória principal

Page 62: Programação Concorrente - Objetos e Concorrência
Page 63: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Investigaremos preocupações de design que surgem no desenvolvimento de software concorrente

• A maioria das construções e padrões de projeto apresentadas mais adiante incluem descrições de como eles resolvem forças aplicáveis discutidas aqui (assim como outras que são menos diretamente vinculadas à concorrência, como a precisão, a realização de testes e assim por diante)

Page 64: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Podemos tomar duas visões complementares de qualquer sistema OO, centrada em objeto e centrada em atividade:

Sistema = Objetos + Atividades

• Sob uma visão centrada em objeto, um sistema é uma coleção de objetos interligados, mas é uma coleção estruturada, não uma sopa de objetos aleatórios

• Objetos se aglomeram em grupos, por exemplo, o grupo de objetos compreendendo uma ParticleApplet, formando assim componentes e subsistemas maiores

Page 65: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Sob uma visão centrada em atividade, um sistema é um conjunto de atividades possivelmente simultâneas

• Em um nível mais refinado, estes são apenas mensagens individuais enviadas (normalmente, chamadas de métodos)

• Eles, por sua vez se organizam em conjuntos de cadeias de chamadas, sequências de eventos, tarefas, sessões, transações e threads

• Uma atividade lógica, como executar o ParticleApplet, pode envolver muitas threads

• Em um nível superior, algumas dessas atividades representam casos de uso de todo o sistema

Page 66: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Nenhuma visão sozinha fornece uma imagem completa de um sistema, uma vez que um determinado objeto pode estar envolvido em várias atividades, e, inversamente, uma determinada atividade pode se estender por vários objetos

• No entanto, essas duas visões dão origem a dois conjuntos complementares de preocupações com correção, uma centrada em objeto e outra centrada em atividade:

– Segurança - nada de ruim acontece a um objeto

– Vivacidade - algo eventualmente acontece dentro de uma atividade

Page 67: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Falhas de segurança levam a um comportamento não intencional em tempo de execução - as coisas começam a acontecer errado

• Falhas em vivacidade podem levar a nenhum comportamento - as coisas param de acontecer

• Infelizmente, algumas das coisas mais simples que podemos fazer para melhorar as propriedades de vivacidade podem destruir propriedades de segurança, e vice-versa

• Torná-los ao mesmo tempo corretos pode ser um desafio

• Devemos equilibrar os efeitos relativos dos diferentes tipos de falha em nossos programas

Page 68: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Mas é uma engenharia padrão (não apenas de engenharia de software) a prática de colocar ênfase no projeto preliminar sobre a segurança

• Quanto mais o seu código realmente importa, o melhor é garantir que um programa não faça nada que possa levar a um comportamento aleatório, até mesmo perigoso

• Por outro lado, a maioria do tempo gasto em ajustes em projetos de concorrência na prática geralmente envolve questões de vivacidade e de eficiência relacionadas à vivacidade

Page 69: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• E as vezes há boas razões para sacrificar seletivamente a segurança pela vivacidade

– Por exemplo, pode ser aceitável para apresentações visuais mostrar transitoriamente um total absurdo de execuções concorrentes descoordenadas - desenhar pixels isolados, indicadores incorretos de progresso ou imagens que não têm relação com suas formas intencionais - se estivermos confiantes de que este estado de coisas vai logo ser corrigido

Page 70: Programação Concorrente - Objetos e Concorrência

Forças de Projeto

• Questões de segurança e vivacidade poderão ser prorrogadas para abranger duas categorias de preocupações de qualidade, uma principalmente centrada em objeto e outra principalmente centrada em vivacidade, que também estão, por vezes, em oposição direta:

– Reutilização - a utilidade de objetos e classes em múltiplos contextos

– Performance - o grau em que as atividades executam logo e rapidamente

Page 71: Programação Concorrente - Objetos e Concorrência
Page 72: Programação Concorrente - Objetos e Concorrência

Segurança

• Práticas de programação concorrente seguras são generalizações de práticas de programação sequenciais seguras e protegidas

• Segurança em projetos concorrentes acrescenta uma dimensão temporal às noções comuns de segurança de tipo

• Um programa com checagem de tipo pode não estar correto, mas pelo menos ele não faz coisas perigosas como interpretar mal os bits que representam um float como se fossem uma referência de objeto

• Da mesma forma, um projeto concorrente seguro pode não ter o efeito pretendido, mas pelo menos ele nunca encontra erros devido à corrupção das representações, por conter threads

Page 73: Programação Concorrente - Objetos e Concorrência

Segurança

• Uma diferença prática entre segurança de tipo e segurança multithreaded é que a maioria dos tópicos de segurança de tipo podem ser verificados automaticamente por compiladores

• Um programa que não consegue passar pela checagem em tempo de compilação não pode sequer ser executado

• A maioria das questões de segurança multithreaded, no entanto, não podem ser verificadas automaticamente, e por isso deve contar com a disciplina do programador

• As técnicas para garantir a segurança descritas aqui dependem de práticas cuidadosas de engenharia (incluindo vários com raízes em formalismos) em vez de os próprios métodos formais

Page 74: Programação Concorrente - Objetos e Concorrência

Segurança

• Segurança multithreaded também adiciona uma dimensão temporal para o projeto e técnicas de programação em torno da segurança

• Práticas de programação segura desabilitam o acesso a determinadas operações nos objetos e recursos a partir de certos invocadores ou aplicações

• Controle de concorrência introduz desativação temporária de acesso com base no exame das ações em curso realizadas por outras threads

• O principal objetivo na preservação da segurança é garantir que todos os objetos em um sistema mantenham estados consistentes: estados em que todos os campos, e todos os campos de outros objetos dos quais dependem, possuam valores legais, significativos

Page 75: Programação Concorrente - Objetos e Concorrência

Segurança

• Às vezes preciso muito trabalho para determinar exatamente o que "legal" e o que "significativo" representam em uma classe particular

• Um caminho é primeiro estabelecer invariantes de nível conceitual, por exemplo, a regra de que os volumes de tanques de água devem estar sempre entre zero e as suas capacidades

• Estas geralmente podem ser reformulados em termos de relações entre os valores de campos nas classes concretas associadas

• Um objeto é consistente se todos os campos obedecem seus invariantes

• Cada método público em cada classe deve levar um objeto de um estado consistente para outro

Page 76: Programação Concorrente - Objetos e Concorrência

Segurança

• Objetos seguros podem ocasionalmente entrar em estados transitoriamente inconsistentes no meio de métodos, mas nunca tentar iniciar novas ações quando estão em estados inconsistentes

• Se cada objeto é projetado para executar ações somente quando é logicamente capaz de fazê-lo, e se toda a mecânica está devidamente implementada, então podemos ter certeza de que um aplicativo usando esses objetos não vai encontrar quaisquer erros devido a inconsistência do objeto

Page 77: Programação Concorrente - Objetos e Concorrência

Segurança

• Uma das razões para ser mais cuidadoso sobre invariantes em programas concorrentes é a de que é muito mais fácil quebrá-las inadvertidamente que na maioria dos programas sequenciais

• A necessidade de proteção contra os efeitos da inconsistência surge mesmo em contextos sequenciais, por exemplo, quando processando exceções e retornos de chamada e ao fazer auto-chamadas a partir de um método em uma classe para outro, no entanto, estas questões tornam-se muito mais centrais em programas concorrentes

• As formas mais comuns para garantir consistência empregam técnicas de exclusão para garantir a atomicidade de ações públicas – que cada ação seja executada até o fim, sem interferência de outros

Page 78: Programação Concorrente - Objetos e Concorrência

Segurança

• Sem essa proteção, inconsistências em programas concorrentes podem resultar de condições de corrida produzindo conflitos de armazenamento no nível de células de memória:

– Conflitos de leitura/gravação - uma thread lê um valor de um campo enquanto outra escreve nele, o valor visto pela thread de leitura é difícil de prever, já que depende de qual thread ganhou a "corrida" para acessar o campo pela primeira vez, o valor lido não precisa nem mesmo ser um valor que já foi escrito por qualquer thread

– Conflitos escrita/escrita - duas threads tentam escrever no mesmo campo, o valor visto na próxima leitura é novamente difícil ou impossível de prever

Page 79: Programação Concorrente - Objetos e Concorrência

Segurança

• É igualmente impossível prever as consequências das ações que são tentadas quando os objetos estão em estados inconsistentes

• Os exemplos incluem: – Uma representação gráfica (por exemplo de uma partícula) é

apresentada num local que o objeto nunca efetivamente ocupou

– Um saldo de conta bancária está incorreto após uma tentativa de retirar o dinheiro no meio de uma transferência automática

– Seguir o próximo ponteiro de uma lista ligada leva a um nó que ainda não está na lista

– Duas atualizações simultâneas de sensores fazem com que um controlador de tempo real realize uma ação com efeito incorreto

Page 80: Programação Concorrente - Objetos e Concorrência

Atributos e Restrições

• Técnicas de programação segura confiam na compreensão clara das propriedades e restrições necessárias que cercam representações de objetos

• Os desenvolvedores que não estão cientes dessas propriedades raramente fazem um trabalho muito bom em preservá-los

• Muitos formalismos estão disponíveis para precisamente representar predicados que descrevem requisitos

– Estes podem ser muito úteis, mas aqui vamos manter a precisão suficiente sem a introdução de formalismos

Page 81: Programação Concorrente - Objetos e Concorrência

Atributos e Restrições

• Requisitos de consistência, por vezes, resultam de definições conceituais de alto nível de atributos realizadas durante o projeto inicial de classes

• Estas restrições normalmente existem independentemente de como os atributos são concretamente representados e acessados via campos e métodos

• Isto foi visto, por exemplo, no desenvolvimento da classe Watertank

Page 82: Programação Concorrente - Objetos e Concorrência

Atributos e Restrições

• Aqui estão alguns outros exemplos:

– Uma ContaBancaria tem um saldo que é igual à soma de todos os depósitos e retiradas menos juros e taxas de serviço

– Um Pacote tem um destino que deve ser um endereço IP legal

– Um Contador tem um valor de contagem integral não-negativo

– Uma Fatura tem um pagamento devido que reflete as regras de um sistema de pagamento

– Um Termostato tem uma temperatura igual à leitura mais recente do sensor

– Uma Figura tem uma localização, dimensão e cor que obedecem um conjunto de diretrizes de estilo para um determinado conjunto de ferramentas GUI

Page 83: Programação Concorrente - Objetos e Concorrência

Atributos e Restrições

• Aqui estão alguns outros exemplos (continuação): – Um BufferLimitado tem um contadorDeElementos que

está sempre entre zero e uma capacidade

– Uma Pilha tem um tamanho e, quando não estiver vazia, um elemento de topo

– Uma Janela tem um conjuntoDePropriedades que mantém mapeamentos atuais de fontes, cor de fundo, etc.

– Um Intervalo tem uma dataInicial que não seja posterior a sua dataFinal

Page 84: Programação Concorrente - Objetos e Concorrência

Atributos e Restrições

• Enquanto tais atributos essencialmente sempre de alguma maneira são mapeados em campos de objetos, as correspondências não precisam ser diretas

• Por exemplo, o topo de uma Pilha não é normalmente armazenado em uma variável, mas, em vez disso em um elemento de uma matriz ou nó de lista ligada

• Além disso, alguns atributos podem ser calculados ("derivados") através de outros, por exemplo, o atributo booleano descoberto de uma ContaBancaria pode ser calculado através da comparação do saldo com zero

Page 85: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Outras restrições e invariantes que são feitas para uma determinada classe normalmente surgem como decisões de implementação adicionais

• Campos declarados em prol da manutenção de uma estrutura de dados particular, para melhorar o desempenho, ou para outros fins de controle interno, muitas vezes precisam respeitar um conjuntos de invariantes

Page 86: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Campos e restrições podem ser classificados em diversas categorias, como apresentado a seguir

• Representações de valor direto - os campos necessários para implementar atributos concretos, por exemplo, um buffer pode ter um campo putIndex mantendo a posição do índice do array, usado para inserir o próximo elemento adicionado

• Representações de valor armazenado em cache - campos usados para eliminar ou minimizar a necessidade de cálculos ou invocações de método, por exemplo, em vez de calcular o valor de descoberto cada vez que for necessário, uma ContaBancaria pode manter um campo descoberto que é verdadeiro se, e somente se, o saldo atual é inferior a zero

Page 87: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Representações de estado lógico - reflexões do estado de controle lógico, por exemplo, um BankCardReader pode ter um campo cartão que representa o cartão que está sendo lido, e um campo validPIN que registra se o código de acesso PIN foi verificado, o campo validPIN do CardReader pode ser usado para controlar o ponto em um protocolo em que o cartão foi lido com sucesso e então validado – Algumas representações de estado assumem a forma de

variáveis de regras, controlando respostas a todo um conjunto de métodos relacionados (às vezes aquelas declaradas em uma única interface), por exemplo, um objeto de jogo pode alternar entre papéis ativo e passivo, dependendo do valor de um campo whoseTurn

Page 88: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Variáveis de estado de execução - campos que registram o estado dinâmico de um objeto, por exemplo, o fato de que uma determinada operação está em andamento – Variáveis de estado de execução podem representar o fato de que

uma determinada mensagem foi recebida, que a ação correspondente foi iniciada, que a ação tenha terminado e que uma resposta à mensagem foi emitida

– Uma variável de estado de execução é muitas vezes um tipo enumerado com os valores representando o estado, por exemplo, CONECTANDO, ATUALIZANDO, EMESPERA

– Outro tipo comum de variável de estado de execução é um contador que registra o número de entradas ou saídas de algum método

– Objetos em programas concorrentes tendem a requerer mais variáveis do que aqueles em contextos sequenciais, para ajudar a controlar e gerenciar o progresso dos métodos que executam de forma assíncrona

Page 89: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Variáveis de história - representações da história ou estados passados de um objeto

– A representação mais ampla é um log da história, registrando todas as mensagens já recebidas e enviadas, juntamente com todas as ações internas correspondentes a mudanças de estado que tenham sido iniciadas ou concluídas

– Subconjuntos menos extensos são muito mais comuns, por exemplo, uma classe BankAccount poderia manter um campo lastSavedBalance que mantém o último valor de checkpoint e poderia ser utilizado para reverter operações canceladas

Page 90: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Variáveis de controle de versão - um inteiro, marcador de tempo, objeto de referência, código de assinatura, ou outra representação indicando o tempo, ordem, ou a natureza da última mudança de estado feito por um objeto, por exemplo, um Termostato pode incrementar um numeroDeLeitura ou gravar o ultimoHorarioDeLeitura quando atualizar sua Temperatura

• Referências a conhecidos - campos apontando para outros objetos que o host interage, mas que não restrigem-se ao estado lógico do host, por exemplo, um alvo de callback de um EventDispatcher, ou um requestHandler delegado por um WebServer

Page 91: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Referências a objetos de representação - os atributos que são conceitualmente mantidos por um objeto host, mas que são, na verdade, gerenciados por outros objetos auxiliares

– Campos de referência podem apontar para outros objetos que auxiliam na representação do estado do objeto host

– Assim, o estado lógico de qualquer objeto pode incluir os estados de objetos que ele possui referência

– Além disso, os próprios campos de referência fazem parte do estado concreto do objeto host

– Todas as tentativas para garantir a segurança devem levar esses relacionamentos em consideração

Page 92: Programação Concorrente - Objetos e Concorrência

Restrições Representacionais

• Referências a objetos de representação (exemplos):

– Uma Pilha pode ter um campo headOfLinkedList para manter o primeiro nó de uma lista que representa a pilha

– Um objeto Pessoa pode manter um campo homePageUrl

mantido como um objeto java.net.URL

– O saldo de um BankAccount pode ser mantido em um repositório central, caso em que o BankAccount poderia manter um campo com referência ao repositório (a fim de consultar o saldo atual), neste caso, uma parte do estado lógico do BankAccount na realidade é gerenciado pelo repositório

Page 93: Programação Concorrente - Objetos e Concorrência
Page 94: Programação Concorrente - Objetos e Concorrência

Vivacidade

• As preocupações de segurança devem ser equilibradas por preocupações de vivacidade

• Algumas propriedades "liveness" podem ser construídas como propriedades de segurança de conjuntos de objetos thread

• Por exemplo, o deadlock-freedom pode ser definido evitando-se o mau estado no qual um conjunto de threads infinitamente esperam umas pelas outras

• Em sistemas “vivos”, todas as atividades eventualmente progridem em direção à conclusão, cada método chamado eventualmente é executado, mas uma atividade pode (talvez apenas transitoriamente) não progredir por qualquer uma das várias razões inter-relacionadas apresentadas a seguir

Page 95: Programação Concorrente - Objetos e Concorrência

Vivacidade

• Bloqueio (locking) – um método sincronizado bloqueia uma thread porque outra thread mantém o bloqueio

• Esperar (waiting) - um método bloqueia (via Object.wait ou seus derivados) à espera de um evento, mensagem, ou condição que ainda tem de ser produzida dentro de outra thread

• Entrada (input)- um método baseado em IO espera por uma entrada que ainda não chegou a partir de um outro processo ou dispositivo

• Contenção de CPU - uma thread não consegue executar mesmo que esteja em um estado executável porque outras threads, ou mesmo programas completamente separados em execução no mesmo computador, estão ocupando CPU ou outros recursos computacionais

Page 96: Programação Concorrente - Objetos e Concorrência

Vivacidade

• Falha (failure) - um método executando em uma thread encontra uma exceção prematura, erro ou falha

• Bloqueios momentâneos em threads em progresso são geralmente aceitáveis, na verdade, bloqueios frequentes de curta duração é intrínseco a muitos estilos de programação concorrente

• O ciclo de vida típico de uma thread pode incluir um número de bloqueios transientes e reescalonamentos, no entanto, a falta permanente ou ilimitada de progresso é geralmente um problema sério

Page 97: Programação Concorrente - Objetos e Concorrência

Ciclo de Vida Típico de uma Thread

Criada

Pronta para

execução

Em execução

Bloqueada

Concluída

início

agendamento

bloqueio desbloqueio retorno, falha

Page 98: Programação Concorrente - Objetos e Concorrência

Vivacidade

• Exemplos de falhas liveness potencialmente permanentes incluem:

– Impasse (deadlock) - dependências circulares entre bloqueios, no caso mais comum, thread A mantém um bloqueio para o objeto X e, em seguida, tenta conseguir um bloqueio para o objeto Y; simultaneamente, a thread B já mantém um bloqueio para o objeto Y e tenta conseguir um bloqueio para o objeto X; nenhuma das threads podem mais fazer progressos

– Sinais perdidos (missed signals) – uma thread permanece dormente porque começou a esperar depois que uma notificação para despertá-la foi produzida

Page 99: Programação Concorrente - Objetos e Concorrência

Vivacidade

• Exemplos de falhas liveness potencialmente permanentes incluem (continuação):

– Bloqueios de monitoramento aninhados - uma thread em espera mantém um bloqueio que poderia ser necessário a qualquer outra thread que tente despertá-la

– Livelock - uma ação contínua de nova tentativa (retry), continuamente falha

– Inanição (starvation) - a JVM/SO falha, nunca alocando tempo de CPU para uma thread, o que pode acontecer devido a políticas de agendamento ou mesmo ataques hostis de negação de serviços, no computador host

Page 100: Programação Concorrente - Objetos e Concorrência

Vivacidade

• Exemplos de falhas liveness potencialmente permanentes incluem (continuação):

– Esgotamento de recursos - um grupo de threads em conjunto detêm todos de um número finito de recursos, uma delas necessita de recursos adicionais, mas nenhuma thread irá desistir em favor de outra

– Falha Distribuída - uma máquina remota conectada por um socket servindo como um InputStream falha ou se torna inacessível

Page 101: Programação Concorrente - Objetos e Concorrência
Page 102: Programação Concorrente - Objetos e Concorrência

Performance

• Forças baseadas no desempenho estendem conceitos de vivacidade

• Além de exigir que cada método invocado, eventualmente, execute, metas de desempenho os obrigam a executar breve e de maneira rápida

• Embora não consideraremos sistemas de tempo real hard, em que falhas de execução dentro de um determinado intervalo de tempo podem levar a erros catastróficos do sistema, quase todos os programas concorrentes têm metas de desempenho implícitas ou explícitas

Page 103: Programação Concorrente - Objetos e Concorrência

Performance

• Requisitos de desempenho significativos são expressos em termos de qualidades mensuráveis, incluindo as métricas apresentadas a seguir

• O objetivo pode ser expresso em uma tendência central (por exemplo, média, mediana) das medições, assim como em sua variabilidade (por exemplo, faixa de valores, desvio padrão)

Page 104: Programação Concorrente - Objetos e Concorrência

Medidas de Desempenho

• Rendimento (throughput) - o número de operações realizadas por unidade de tempo, as operações de interesse podem variar de métodos individuais até a execução inteira do programa – Na maioria das vezes, o rendimento não é relatado como uma taxa,

mas em vez disso, como o tempo levado para executar uma operação

• Latência - o tempo decorrido entre a emissão de uma mensagem (via por exemplo, um clique do mouse, a invocação de método, ou conexão de socket de entrada) e de sua realização – Em contextos onde as operações são uniformes, single-threaded, e

"continuamente" solicitadas, a latência é apenas o inverso do rendimento

– Mas, mais tipicamente, as latências de interesse refletem os tempos de resposta - os atrasos até que algo aconteça, não necessariamente a conclusão integral de um método ou serviço

Page 105: Programação Concorrente - Objetos e Concorrência

Medidas de Desempenho

• Capacidade - o número de atividades simultâneas que podem ser realizadas por um determinado alvo em um rendimento mínimo ou latência máxima

– Especialmente em aplicações de rede, isso pode servir como um indicador útil de disponibilidade geral, uma vez que reflete o número de clientes que podem ser atendidos sem deixar a conexões cair devido a time-outs ou sobrecargas da fila da rede

• Eficiência - rendimento dividido pela quantidade de recursos computacionais (por exemplo, CPUs, memória e dispositivos IO) necessários para obter esse rendimento

Page 106: Programação Concorrente - Objetos e Concorrência

Medidas de Desempenho

• Escalabilidade - a taxa à qual a latência ou o rendimento melhora quando os recursos (novamente, geralmente CPUs, memória, ou dispositivos) são adicionados a um sistema

– Medidas relacionadas incluem utilização – o percentual de recursos disponíveis que são aplicados a uma tarefa de interesse

• Degradação - a taxa em que a latência ou o rendimento piora à medida que mais clientes, atividades ou operações são adicionados sem a adição de recursos

Page 107: Programação Concorrente - Objetos e Concorrência

Performance

• A maioria dos projetos multithreaded aceitam implicitamente um pequeno trade-off de eficiência computacional ruim para obter uma melhor latência e escalabilidade

• Suporte a concorrência introduz os tipos de sobrecarga e de contenção apresentados a seguir, que podem tornar programas lentos

Page 108: Programação Concorrente - Objetos e Concorrência

Sobrecarga e Contenção

• Bloqueios - um método sincronizado normalmente requer uma maior sobrecarga de chamada do que um método não sincronizado, além disso, os métodos que frequentemente bloqueiam esperam pelos bloqueios (ou por qualquer outro motivo), sendo mais lentos do que aqueles que não o fazem

• Monitores - Object.wait, Object.notify, Object.notifyAll, e os métodos deles derivados (como Thread.join) podem ser mais custosos do que outras operações de suporte a tempo de execução da JVM básica

• Threads - criar e iniciar uma thread é tipicamente mais caro do que a criação de um objeto comum e chamar um método dele

Page 109: Programação Concorrente - Objetos e Concorrência

Sobrecarga e Contenção

• Mudança de contexto (context-switching) - o mapeamento de threads para CPUs encontra uma sobrecarga de mudança de contexto quando a JVM/OS salva o estado da CPU associado a uma thread, seleciona outra thread a ser executada, e carrega o estado da CPU associado

• Agendamento - cálculos e políticas subjacentes que selecionam qual thread está elegíveis para executar adiciona uma sobrecarga, estas podem ainda interagir com outras tarefas do sistema, tais como processamento assíncrono de eventos e coleta de lixo

Page 110: Programação Concorrente - Objetos e Concorrência

Sobrecarga e Contenção

• Localidade - em multiprocessadores, quando várias threads estão em execução em diferentes CPUs compartilhando o acesso aos mesmos objetos, hardware de consistência de cache e software de sistema de baixo nível devem comunicar os valores associados entre os processadores

Page 111: Programação Concorrente - Objetos e Concorrência

Sobrecarga e Contenção

• Algoritmos - alguns algoritmos sequenciais eficientes não se aplicam em ambientes concorrentes

– Por exemplo, algumas estruturas de dados que dependem de rotinas de cache onde apenas se sabe que exatamente uma tread executa todas as operações

– No entanto, também existem algoritmos concorrentes alternativos eficientes para muitos problemas, incluindo aqueles que criam a possibilidade de novos aumentos de velocidade através do paralelismo

• As sobrecargas gerais associadas com construtores de concorrência diminuem de forma constante com a melhora das JVMs

Page 112: Programação Concorrente - Objetos e Concorrência
Page 113: Programação Concorrente - Objetos e Concorrência

Reusabilidade

• Uma classe ou objeto é reutilizável na medida em que ela pode ser facilmente empregada em diferentes contextos, tanto como um componente de caixa preta ou como a base de extensão de caixa branca através de subclasses e técnicas relacionadas

• A interação entre as preocupações de segurança e vivacidade podem afetar significativamente a capacidade de reutilização

• Geralmente é possível projetar componentes para serem seguros em todos os contextos possíveis – Por exemplo, um método sincronizado que se recusa a iniciar

até que ele possua o bloqueio de sincronização irá fazer isso, não importa como ele é usado

Page 114: Programação Concorrente - Objetos e Concorrência

Reusabilidade

• Mas em alguns desses contextos, programas usando esse componente seguro pode encontrar falhas de vivacidade (por exemplo, o deadlock)

• Por outro lado, a funcionalidade em torno de um componente utilizando apenas métodos não sincronizados será sempre “vivo” (pelo menos no que diz respeito ao bloqueio), mas podem encontrar violações de segurança quando múltiplas execuções concorrentes ocorrem

Page 115: Programação Concorrente - Objetos e Concorrência

Reusabilidade

• As dualidades de segurança e vivacidade são refletidas em alguns pontos de vista extremos de metodologia de projeto: – Algumas estratégias de projeto top-down tomam uma abordagem de

segurança pura em primeiro lugar: certifica-se de que cada classe e objeto é seguro, e, posteriormente, tenta melhorar a vivacidade como uma medida de otimização

– Em oposto, a abordagem bottom-up é muitas vezes adotada em programação de sistemas concorrentes: certifica-se de que o código é executado, e, em seguida, tenta dividi-lo em camadas de recursos de segurança, por exemplo, adicionando bloqueios

• Nenhum dos extremos é especialmente bem sucedido na prática: é muito fácil que abordagens top-down resultem em sistemas lentos, propensos a impasses, e que abordagens bottom-up resultem em código com erros, com violações de segurança não previstas

Page 116: Programação Concorrente - Objetos e Concorrência

Reusabilidade

• Geralmente é mais produtivo prosseguir com o entendimento de que alguns componentes muito úteis e eficientes não são, e não precisam ser, absolutamente seguros, e que serviços úteis realizados por alguns componentes não são absolutamente “vivos”

• Em vez disso, eles operam corretamente somente em determinados contextos de uso restrito

• Portanto, estabelecer, documentar, publicar e explorar esses contextos tornam-se questões centrais no projeto de software concorrente

Page 117: Programação Concorrente - Objetos e Concorrência

Reusabilidade

• Existem duas abordagens gerais (e uma gama de opções intermédias) para lidar com a dependência de contexto:

– (1) minimizar a incerteza “fechando” partes de sistemas, e

– (2) estabelecer políticas e protocolos que permitam que os componentes se tornem ou permaneçam abertos

• Muitos esforços práticos de design envolvem um pouco de cada

Page 118: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

Page 119: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

• Um sistema fechado ideal é aquele para o qual temos de maneira perfeita e estática (tempo de projeto) o conhecimento sobre todos os comportamentos possíveis

• Isto é tipicamente tanto inatingível como indesejável, no entanto, muitas vezes é possível fechar partes de sistemas, em unidades individuais que variam a partir de classes de componentes a nível de produto, através do emprego de versões possivelmente extremas de técnicas de encapsulamento OO apresentadas a seguir

Page 120: Programação Concorrente - Objetos e Concorrência

Técnicas de Encapsulamento OO

• Comunicação externa restrita - todas as interações, tanto internas como externas, ocorrem através de uma interface reduzida, no caso mais tratável, o subsistema é fechado à comunicação, nunca internamente invocando métodos em objetos fora do subsistema

• Estrutura interna determinística - a natureza concreta de todos os objetos e threads que compõem o subsistema é estaticamente conhecida, as palavras-chave final e private podem ser usadas para ajudar a impor isso

Page 121: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

• Em pelo menos alguns desses sistemas, podemos, em princípio, provar - informalmente, formalmente, ou até mesmo mecanicamente - que não são possíveis violações de segurança interna ou de vivacidade, dentro de um componente fechado

• Ou, se elas são possíveis, podemos continuar a refinar projetos e implementações até que um componente esteja provavelmente correto

• No melhor dos casos, podemos, então, aplicar esse conhecimento de composição para analisar outras partes de um sistema que contam com este componente

Page 122: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

• Informações estáticas perfeitas sobre objetos, threads e interações nos dizem não só o que pode acontecer, mas também o que não pode acontecer

– Por exemplo, pode ser o caso que, embora dois métodos sincronizados em dois objetos contenham chamadas um para o outro, eles nunca podem ser acessados simultaneamente por diferentes threads dentro do subsistema, desse modo o impasse nunca ocorrerá

Page 123: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

• Fechamento também pode apresentar novas oportunidades de otimização manual ou dirigida pelo compilador

– Por exemplo, a remoção de sincronização de métodos que normalmente poderiam requere-lo, ou empregar algoritmos inteligentes de propósito especial que podem ser desenvolvidos para aplicação apenas pela eliminação de possibilidade de interação indesejada

– Sistemas embarcados são muitas vezes compostos como coleções de módulos fechados, em parte para melhorar a previsibilidade, escalonabilidade e performance

Page 124: Programação Concorrente - Objetos e Concorrência

Subsistemas Fechados

• Embora os subsistemas fechados sejam tratáveis, eles também podem ser frágeis

• Quando as restrições e premissas que governam a sua estrutura interna mudam, esses componentes são muitas vezes descartados e reconstruídos a partir do zero

Page 125: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Um sistema aberto ideal é infinitamente extensível, através de várias dimensões:

– Pode carregar classes desconhecidas de forma dinâmica, permitir que subclasses substituam qualquer método, empregar callbacks entre objetos de diferentes subsistemas, compartilhar recursos comuns entre threads, usar reflexão para descobrir e invocar métodos em objetos de outra maneira desconhecidos, e assim por diante

• Abertura ilimitada é geralmente tão inatingível e indesejável quanto fechamento completo: se tudo pode mudar, então não podemos programar nada

• Mas a maioria dos sistemas exigem, pelo menos, alguma dessa flexibilidade

Page 126: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Análise estática completa de sistemas abertos não é possível ainda, uma vez que a sua natureza e estrutura evoluem ao longo do tempo

• Em vez disso, os sistemas abertos devem contar com políticas e protocolos documentados a que cada componente adere

• A Internet é um dos melhores exemplos de um sistema aberto: – Ela continuamente evolui, por exemplo, adicionando novos

hospedeiros, páginas web, e serviços, exigindo apenas que todos os participantes obedeçam a algumas políticas e protocolos de rede

– Tal como acontece com outros sistemas abertos, a aderência às políticas e protocolos da Internet é, por vezes, difícil de aplicar

– No entanto, JVMs se organizam de forma que os componentes com não-conformidade não podem catastroficamente danificar a integridade do sistema

Page 127: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Projeto orientado por políticas pode funcionar bem em nível muito menor de sistemas concorrentes típicos, onde as políticas e protocolos muitas vezes tomam a forma de regras de projeto

• Exemplos de domínios de políticas incluem:

– Fluxo - por exemplo, uma regra na forma: componentes do tipo A enviam mensagens para os do tipo B, mas nunca vice-versa

– Bloqueio - por exemplo, uma regra na forma: métodos do tipo A sempre disparam exceções imediatamente se o recurso R não está disponível, em vez de bloquear até que esteja disponível

– Notificações - por exemplo, uma regra na forma: Objetos do tipo A sempre enviam notificações de mudança aos seus ouvintes (listeners), sempre que atualizados

Page 128: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Adopção de um número relativamente pequeno de políticas simplifica o projeto, minimizando a possibilidade de decisões inconsistentes caso a caso

• Autores de componentes, talvez com a ajuda de revisores de código e ferramentas, precisam checar apenas se eles estão obedecendo as regras de projeto relevantes, e de outra forma podem focar a atenção nas tarefas em mãos, desenvolvedores podem pensar localmente, enquanto ainda agindo globalmente

Page 129: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• No entanto, o design orientado a política pode se tornar incontrolável quando o número de políticas cresce muito e as obrigações de programação que elas induzem sobrecarregam desenvolvedores

• Quando os métodos, mesmo simples, como atualização de um saldo de conta ou impressão "Olá, mundo" exigem dezenas de linhas de código estranho propenso a erros de conformidade com políticas de projeto, é hora de tomar algum tipo de ação de reparação: – Simplificar ou reduzir o número de políticas – Criar ferramentas que ajudam a automatizar a geração de código e/ou

a verificação de conformidade – Criar linguagens específicas de domínio que impõem uma

determinada disciplina; ou – Criar ambientes e bibliotecas que reduzam a necessidade de que

tanto código de suporte tenha que ser escrito dentro de cada método

Page 130: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Enquanto induzir maior fechamento nos permite otimizar o desempenho, realizar uma maior abertura permite que otimizemos para mudanças futuras

• Estes dois tipos de afinações e refatorações são muitas vezes igualmente desafiadoras, mas têm efeitos opostos: – Otimizar para desempenho geralmente implica em explorar

casos especiais por decisões de projeto hard-wired (conexão direta permanente)

– Otimizar para extensibilidade implica na remoção de decisões hard-wired permitindo a variação, por exemplo, através de encapsulamento através de métodos substituíveis, ou abstração da funcionalidade através de interfaces que podem ser re-implementadas de formas completamente diferentes por componentes carregados dinamicamente

Page 131: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• Porque programas concorrentes tendem a incluir mais decisões políticas do que os programas sequenciais, e tendem a depender mais pesadamente de invariantes envolvendo escolhas particulares de representação, classes que envolvem construtores de concorrência muitas vezes acabam por exigir uma atenção especial, a fim de serem facilmente extensíveis

• Este fenômeno é muito comum, o suficiente para ter um nome especial, chamado anomalia de herança

Page 132: Programação Concorrente - Objetos e Concorrência

Subsistemas Abertos

• No entanto, algumas outras técnicas de programação desnecessariamente restringem extensibilidade por razões de desempenho

• Essas táticas se tornam mais questionáveis em melhora de compiladores e JVMs

• Por exemplo, a compilação dinâmica permite que muitos componentes extensíveis sejam tratados como se eles fossem fechados no momento de carregamento da classe, levando a otimizações e especializações que exploram determinados contextos de tempo de execução de forma mais eficaz do que qualquer programador poderia

Page 133: Programação Concorrente - Objetos e Concorrência

Documentação

• Quando a composicionalidade é dependente do contexto, é vital para o uso pretendido, que contextos e restrições envolvendo componentes sejam bem compreendidos e bem documentados

• Quando esta informação não é fornecida, utilização, reutilização, manutenção, testes, gerenciamento de configuração, a evolução do sistema e as preocupações de engenharia de software relacionadas se tornam muito mais difíceis

Page 134: Programação Concorrente - Objetos e Concorrência

Documentação

• A documentação pode ser usada para melhorar a compreensibilidade por qualquer um dos vários públicos: outros desenvolvedores usando uma classe como um componente de caixa-preta, autores de subclasses, os desenvolvedores que mais tarde irão manter, modificar ou reparar o código, testadores e revisores de código, e usuários do sistema

• Através destas audiências, o primeiro objetivo é o de eliminar a necessidade de uma documentação extensa, minimizando o inesperado, e, portanto, reduzindo a complexidade conceitual via: padronização, clareza e código auxiliar

Page 135: Programação Concorrente - Objetos e Concorrência

Padronização

• Usando políticas, protocolos e interfaces comuns

• Por exemplo:

– A adoção de padrões de projeto, referenciando livros, páginas da web ou documentos de projeto que os descrevem de maneira mais plena

– Empregando bibliotecas utilitárias padrão e frameworks

– Usando idiomas de codificação padrão e convenções de nomes

– Realizando buscas através de listas de revisão padrão que enumeram erros comuns

Page 136: Programação Concorrente - Objetos e Concorrência

Clareza

• Utilizando as expressões de código mais simples e em sua maioria autoexplicativas

• Por exemplo:

– Utilizar exceções para advertir sobre condições verificadas

– Expressando restrições internas via qualificadores de acesso (como private)

– Adoção de padrões comuns de nomenclatura e assinatura, por exemplo, determinação de que, salvo indicação em contrário, os métodos que podem bloquear declaram que eles lançam uma InterruptedException

Page 137: Programação Concorrente - Objetos e Concorrência

Código Auxiliar

• Fornecimento de código que demonstra usos pretendidos

• Por exemplo:

– Incluindo amostras ou exemplos de uso recomendadas

– Fornecimento de trechos de código que alcançam efeitos não-óbvios

– Incluindo métodos projetados a servir como auto testes

Page 138: Programação Concorrente - Objetos e Concorrência

Documentação

• Após eliminar a necessidade de explicações óbvias via documentação, formas mais úteis de documentação podem ser usadas para esclarecer decisões de projeto

• Os detalhes mais críticos podem ser expressos de forma sistemática, usando anotações semiformais mostradas na tabela a seguir

Page 139: Programação Concorrente - Objetos e Concorrência

Anotações Semiformais

PRE Pré-condição (não necessariamente checada) /** PRE: Caller holds synch lock …

WHEN Condição de guarda (sempre checada) /** WHEN: not empty return oldest …

POST Pós-condição (geralmente não checada) /** POST: Resource r is released …

OUT Garantia de envio de mensagem (por exemplo callback) /** OUT: c.process(buff) called after read …

RELY Propriedade requerida (geralmente não checada) de outros objetos e métodos /** RELY: Must be awakened by x.signal() …

INV Uma restrição de objeto verdadeira no início e no final de cada método público /** INV: x,y are valid screen coordinates …

INIT Uma restrição de objeto que deve ser mantida na construção /** INIT: bufferCapacity greater than zero …

Page 140: Programação Concorrente - Objetos e Concorrência

Documentação

• Documentação adicional, menos estruturada pode ser usada para explicar as restrições não-óbvias, limitações contextuais, suposições e decisões de design que impactem o uso em um ambiente concorrente

• É impossível fornecer uma lista completa de construções que necessitam desse tipo de documentação, mas casos típicos incluem: – Informações de design de alto nível sobre o estado e as restrições de

métodos

– Limitações de segurança conhecidas devido à falta de bloqueio em situações que exigem isso

– O fato de um método poder bloquear indefinidamente à espera de uma condição, evento ou recurso

– Métodos destinados a serem chamados apenas a partir de outros métodos, talvez em outras classes

Page 141: Programação Concorrente - Objetos e Concorrência
Page 142: Programação Concorrente - Objetos e Concorrência

Padrões de Projeto

• Muitos projetos concorrentes são melhor descritos como padrões

• Um padrão encapsula uma forma de projeto comum e bem sucedida, geralmente uma estrutura de objeto (também conhecida como um microarquitetura) que consiste em uma ou mais interfaces, classes e/ou objetos que obedecem a certas restrições e relacionamentos estáticos e dinâmicos

• Padrões são um veículo ideal para a caracterização de desenhos e técnicas que não precisam ser implementadas exatamente da mesma maneira em diferentes contextos, e, portanto, não podem ser encapsuladas de maneira útil como componentes reutilizáveis

Page 143: Programação Concorrente - Objetos e Concorrência

Padrões de Projeto

• Os componentes reutilizáveis e frameworks podem desempenhar um papel central no desenvolvimento de software concorrente, mas grande parte da programação OO concorrente implica em reutilização, adaptação e extensão das recorrentes formas e práticas de projeto e não de classes particulares

Page 144: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Estratificação de controle de políticas sobre mecanismos é um princípio estruturante comum em sistemas de todos os tipos

• Muitas técnicas de estratificação e de composição OO contam com “imprensar” alguma chamada de método ou corpo de código entre ações antes e depois

• Todas as formas de controle antes/depois providenciam que um determinado método seja interceptado por isso, sempre executam a sequência:

before(); method(); after();

• Ou, para assegurar que ações depois sejam realizadas mesmo se os métodos centrais encontrarem exceções:

before();

try { method(); }

finally { after(); }

Page 145: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Por exemplo, um método sincronized adquire um bloqueio antes de executar o código dentro do método, e libera o bloqueio após o método ser concluído

• Mas as ideias básicas de padrões antes/depois podem ser ilustradas em conjunto com outra prática útil na programação OO, código de autocontrole: – Os campos de qualquer objeto devem preservar todas as

invariantes sempre que o objeto não está envolvido em um método público

• Invariantes devem ser mantidas mesmo que esses métodos lancem qualquer uma das suas exceções declaradas, a menos que essas exceções denotem corrupção ou falha do programa (como pode ocorrer em RuntimeExceptions e Errors )

Page 146: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Conformidade com as invariantes computáveis pode ser testada de forma dinâmica através da criação de classes que as verificam tanto sobre a entrada como na saída de cada método público

• Técnicas semelhantes aplicam-se a pré-condições e pós-condições, mas para simplificar, vamos ilustrar apenas com invariantes

• Como exemplo, suponha que nós gostaríamos de criar classes de tanque de água que contêm um autocontrole sobre a invariante que o volume esteja sempre entre zero e a capacidade

• Para fazer isso, podemos definir um método checkVolumeInvariant e usá-lo tanto antes como depois da operação

Page 147: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Podemos definir primeiro uma exceção para lançar se a invariante falhar:

class AssertionError extends java.lang.Error {

public AssertionError() { super(); }

public AssertionError(String message) {

super(message); }

}

• Pode ser perturbador inserir essas verificações manualmente dentro de cada método

• Em vez disso, um dos três padrões de projeto antes/depois pode ser usado para separar os controles dos métodos base: classes de adaptadores, projetos baseados em subclasses, e classes adaptadoras de métodos

Page 148: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Em todos os casos, a melhor maneira de configuração é a definição de uma interface que descreva a funcionalidade básica

• Interfaces são quase sempre necessárias quando precisamos dar espaço suficiente para variação nas implementações

• Por outro lado, a falta de interfaces existentes limita as opções quando retroativamente aplicamos padrões antes/depois

Page 149: Programação Concorrente - Objetos e Concorrência

Aplicação de Camadas

• Aqui está uma interface descrevendo uma variante menor da classe tanque de água, técnicas antes/depois podem ser aplicadas para verificar invariantes em torno da operação transferWater:

interface Tank {

float getCapacity();

float getVolume();

void transferWater(float amount)

throws OverflowException,

UnderflowException;

}

Page 150: Programação Concorrente - Objetos e Concorrência

Adaptadores

• Quando interfaces padronizadas são definidas depois do projeto de uma ou mais classes concretas, essas classes, muitas vezes não implementam a interface desejada, por exemplo, os nomes de seus métodos podem ser ligeiramente diferentes dos definidos na interface

• Se não podemos modificar essas classes concretas para corrigir esses problemas, podemos obter o efeito desejado através da construção de uma classe Adapter que traduz as incompatibilidades

Page 151: Programação Concorrente - Objetos e Concorrência

Adaptadores

• Digamos que tenhamos uma classe Performer que possui o método perform e cumpre todas as qualificações para ser usada como um Runnable exceto pelo desencontro de nomes

• É possível construir um Adapter para que ela possa ser usada em uma thread por alguma outra classe:

Page 152: Programação Concorrente - Objetos e Concorrência

Adaptadores

class AdaptedPerformer implements Runnable {

private final Performer adaptee;

public AdaptedPerformer(Performer p) {

adaptee = p; }

public void run() { adaptee.perform(); }

}

• Este é apenas um dos muitos contextos comuns para construção de adaptadores, que também formam a base de vários padrões relacionados apresentados no livro Design Patterns – Um Proxy é um adaptador com a mesma interface que o seu

delegado

– Um Composite mantém uma coleção de delegados, todos dando suporte à mesma interface

Page 153: Programação Concorrente - Objetos e Concorrência

Adaptadores

• Neste estilo baseado em delegação de composição, a classe host de acesso público delega todos os métodos para um ou mais delegados e recebe de volta respostas, talvez fazendo alguma tradução (mudanças de nome, conversão de parâmetros, filtragem de resultados, etc.) que cerca as chamadas aos delegados

• Os adaptadores podem ser usados para fornecer controle antes/depois meramente envolvendo a chamada delegada dentro das ações de controle

Page 154: Programação Concorrente - Objetos e Concorrência

Adaptadores

• Por exemplo, supondo que temos uma classe de implementação, chamada TankImpl , podemos escrever a seguinte classe AdaptedTank, que pode ser usada em vez da original em alguma aplicação, substituindo todas as ocorrências de: new TankImpl(...)

• Por: new AdaptedTank(new TankImpl(...))

Page 155: Programação Concorrente - Objetos e Concorrência

AdaptedTank

class AdaptedTank implements Tank {

protected final Tank delegate;

public AdaptedTank(Tank t) { delegate = t; }

public float getCapacity() {

return delegate.getCapacity(); }

public float getVolume() {

return delegate.getVolume(); }

protected void checkVolumeInvariant() throws AssertionError {

float v = getVolume();

float c = getCapacity();

if ( !(v >= 0.0 && v <= c) )

throw new AssertionError();

}

Page 156: Programação Concorrente - Objetos e Concorrência

AdaptedTank

public synchronized void transferWater(

float amount) throws OverflowException, UnderflowException {

checkVolumeInvariant(); // before-check

try {

delegate.transferWater(amount);

}

// The re-throws will be postponed until

// after-check in the finally clause

catch (OverflowException ex) { throw ex; }

catch (UnderflowException ex) { throw ex; }

finally {

checkVolumeInvariant(); // after-check

}

}

}

Page 157: Programação Concorrente - Objetos e Concorrência

Subclasses

• No caso normal, quando as versões dos métodos interceptados antes/depois têm os mesmos nomes e usos das versões de base, subclasse pode ser uma alternativa mais simples do que a utilização de adaptadores

• Versões subclasse de métodos podem interpor verificações em torno de chamadas para suas versões super

Page 158: Programação Concorrente - Objetos e Concorrência

Exemplo de Subclasses

class SubclassedTank extends TankImpl {

protected void checkVolumeInvariant() throws AssertionError {

// ... identical to AdaptedTank version ...

}

public synchronized void transferWater(

float amount) throws OverflowException, UnderflowException {

// identical to AdaptedTank version except

// for inner call:

// ...

try {

super.transferWater(amount);

}

// ...

}

}

Page 159: Programação Concorrente - Objetos e Concorrência

Exemplo de Subclasses

Page 160: Programação Concorrente - Objetos e Concorrência

Subclasses X Adaptadores

• Algumas escolhas entre subclasses e adaptadores são apenas uma questão de estilo, outras refletem diferenças entre delegação e herança

• Adaptadores permitem manipulações que escapam às regras de subclassificação, por exemplo, não podemos substituir um método público como privado em uma subclasse, a fim de desabilitar o acesso, mas podemos simplesmente deixar de delegar o método em um adaptador

• Várias formas de delegação podem até ser usadas como um substituto de tipos para subclassificação fazendo com que cada classe "sub" (Adapter) mantenha uma referência a uma instância de sua classe "super" (Adaptee), delegando todas as operações "herdadas"

Page 161: Programação Concorrente - Objetos e Concorrência

Subclasses X Adaptadores

• Delegação também pode ser mais flexível do que subclassificação, uma vez que objetos "sub" podem até mesmo mudar seus "supers" dinamicamente (através da redefinição da referência delegada)

• Delegação pode também ser usada para obter os efeitos de herança múltipla, por exemplo, se uma classe deve implementar duas interfaces independentes, chamadas Tank e java.awt.event.ActionListener, e há duas superclasses disponíveis que fornecem a funcionalidade necessária, então uma destas pode ser uma subclasse e o outro delegado

Page 162: Programação Concorrente - Objetos e Concorrência

Subclasses X Adaptadores

• No entanto, a delegação é menos poderosa do que subclassificação em alguns outros aspectos

• Por exemplo, auto-chamadas em "superclasses" não são automaticamente vinculadas às versões dos métodos que foram “sobrescritos” em “subclasses” baseadas em delegação

• Projetos de adaptadores também podem cair em problemas que giram em torno do fato de que os objetos Adaptee e Adapter são objetos diferentes, por exemplo, testes de igualdade de referência a objetos devem ser realizados com mais cuidado uma vez que um teste para ver se temos a versão Adaptee de um objeto falha se temos a versão Adapter, e vice-versa

Page 163: Programação Concorrente - Objetos e Concorrência

Métodos de Modelo

• Quando estamos bastante certos de que iremos utilizar um controle antes/depois em um conjunto de classes relacionadas, podemos criar uma classe abstrata que automatiza a sequência de controle através de uma aplicação do padrão Template Method (que não tem nada a ver com tipos genéricos C++)

Page 164: Programação Concorrente - Objetos e Concorrência

Métodos de Modelo

Page 165: Programação Concorrente - Objetos e Concorrência

Métodos de Modelo

• Uma classe abstrata que dá suporte a métodos de modelo estabelece um ambiente que facilita a construção de subclasses que podem substituir as ações base, métodos antes/depois, ou ambos: – Código de ação no nível básico é definido em métodos não-

públicos (por convenção, chamamos a versão não-pública de qualquer método method como doMethod), um pouco menos flexíveis, estes métodos não precisam ser declaradas não-públicos se forem concebidos para ser sobrescritos em subclasses

– Operações antes e depois também são definidas como métodos não-públicos

– Métodos públicos invocam os métodos base entre os métodos antes e depois

Page 166: Programação Concorrente - Objetos e Concorrência

Métodos de Modelo

• Aplicando ao exemplo Tank: abstract class AbstractTank implements Tank {

protected void checkVolumeInvariant() throws AssertionError {

// ... identical to AdaptedTank version ...

}

protected abstract void doTransferWater(float amount)

throws OverflowException, UnderflowException;

public synchronized void transferWater(float amount)

throws OverflowException, UnderflowException {

// identical to AdaptedTank version except

// for inner call:

// ...

try {

doTransferWater(amount);

}

// ...

}

}

Page 167: Programação Concorrente - Objetos e Concorrência

Métodos de Modelo

• Aplicando ao exemplo Tank (continuação): class ConcreteTank extends AbstractTank {

protected final float capacity;

protected float volume;

// ...

public float getVolume() { return volume; }

public float getCapacity() { return capacity;}

protected void doTransferWater(float amount)

throws OverflowException,

UnderflowException {

// ... implementation code ...

}

}

Page 168: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

• A abordagem mais flexível, mas, por vezes, mais difícil de controle antes/depois é definir uma classe cujo propósito é o de chamar um método específico em um determinado objeto

• No padrão Command Object e em suas várias versões, as instâncias dessas classes podem então ser passadas, manipuladas, e, finalmente, executadas (aqui, entre as operações antes/depois)

Page 169: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

• Por causa das regras de tipagem estática, deve haver um tipo diferente de classe do adaptador para cada tipo de método que está sendo adaptado

• Para evitar a proliferação de todos esses tipos, a maioria das aplicações restringem a atenção para apenas um ou um pequeno conjunto de interfaces genéricas, cada uma definindo um único método

• Por exemplo, a classe Thread e a maioria dos outros frameworks de execução aceitam apenas instâncias da interface Runnable para invocar seus métodos run, contendo argumentos, resultado e exceções

• Da mesma forma, definimos e usamos a interface Callable contendo apenas um método call que aceita um argumento Object, retorna um Object , e pode lançar qualquer Exception

Page 170: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

• Podemos aplicar uma versão de antes/depois de divisão em camadas com base nos adaptadores de método primeiro pela definição de uma interface TankOp: interface TankOp {

void op() throws OverflowException,

UnderflowException;

}

• No código de exemplo apresentado a seguir, estranhamente, todas as utilizações de adaptadores de método são locais para uma classe TankWithMethodAdapter

• Além disso, neste pequeno exemplo, há apenas um método adaptável

Page 171: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

• No entanto, o mesmo processo poderia ser utilizado para quaisquer outros métodos de Tank definidos nesta classe ou em suas subclasses

• Adaptadores de método são muito mais comuns em aplicações onde instâncias devem ser registradas e/ou transmitidas entre vários objetos antes de serem executadas, o que justifica os custos extras de instalação e obrigações de programação

Page 172: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

class TankWithMethodAdapter {

// ...

protected void checkVolumeInvariant() throws AssertionError {

// ... identical to AdaptedTank version ...

}

protected void runWithinBeforeAfterChecks

(TankOp cmd) throws OverflowException,

UnderflowException {

// identical to AdaptedTank.transferWater

// except for inner call:

// ...

try {

cmd.op();

}

// ...

}

Page 173: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

protected void doTransferWater(float amount)

throws OverflowException,

UnderflowException {

// ... implementation code ...

}

public synchronized void transferWater(final float amount) throws OverflowException,

UnderflowException {

runWithinBeforeAfterChecks(new TankOp() {

public void op() throws OverflowException,

UnderflowException {

doTransferWater(amount);

}

});

}

}

Page 174: Programação Concorrente - Objetos e Concorrência

Adaptadores de Método

• Algumas aplicações de adaptadores de método podem ser parcialmente automatizadas através de recursos de reflexão

• Um construtor genérico pode sondar uma classe para um determinado java.lang.reflect.Method, configurar argumentos, invoca-lo, e transferir de volta resultados

• Isto tem o preço de maior sobrecarga, bem como a necessidade de lidar com as muitas exceções que podem surgir

• Portanto, geralmente só vale a pena quando se lida com código desconhecido carregado dinamicamente

Page 175: Programação Concorrente - Objetos e Concorrência

UNIVERSIDADE ESTADUAL DO SUDOESTE DA BAHIA CURSO DE CIÊNCIA DA COMPUTAÇÃO

PROGRAMAÇÃO CONCORRENTE – 2015.1

Fábio M. Pereira

([email protected])