quinta-feira, 18 de setembro de 2008

Cópia exclusiva de coleção

Por serem de díficil identificação, problemas de concorrência acabam indo pra produção sem sequer serem notado em ambiente de testes e desenvolvimento. Erros acabam ocorrendo de maneira intermitente, causando dores de cabeça e acessos de insanidade nos desenvolvedores. Martin Fowler em seu livro Patterns of Enterprise Application Architecture identifica algumas soluções para evitar problemas de concorrência relativos a integridade de dados. Contudo há um outro problema que não está catalogado neste livro: A concorrência de informações na memória.

Ao guardar objetos em uma coleção que é compartilhada por outras threads, é necessário tomar providências para que não sejam lançadas exceções inesperadas. A medida básica é garantir que os pontos de acesso às coleções compartilhadas devem obter exclusividade sobre seu uso com a diretiva synchronized. Veja abaixo um exemplo de classe que torna o uso de uma lista à prova de erros de concorrência:

public class ListaSegura {
private List lista = new ArrayList();
public void adiciona(Object objeto) {
synchronized (lista) {
lista.add(objeto);
}
}
public void remove(Object objeto) {
synchronized (lista) {
lista.remove(objeto);
}
}
public void listar() {
synchronized (lista) {
for (Object o : lista) {
System.out.println(o);
}
}
}
}

É uma solução simples e que resolve em parte o problema. Contudo na maioria das vezes não podemos obter a exclusividade para iterar sobre uma coleção. No caso mostrado acima isso é possível pois estamos apenas imprimindo o objeto. Mas existem alguns casos em que obter essa exclusividade para a iteração nos traria alguns problemas. Esses casos estão descritos abaixo:

1- Alteração da coleção: No caso de você iterar pela coleção para remover ou adicionar algum objeto a ela. A exclusão ou adição ocorreria no meio da iteração e com isso seria lançada uma exceção de concorrência.Um exemplo simples disso são classes que gerenciam cache e precisam periodicamente remover objetos expirados.

2- Iteração prolongada: Quando a coleção possui objetos demais ou o processo executado durante a iteração é lento, tornando o tempo de exclusividade total muito longo. Isso faria com que o restante do sistema que precisasse utilizar essa coleção ficasse muito tempo aguardando pela exclusividade terminar. Um exemplo comum é a execução de métodos que acessem banco de dados dentro da iteração de um objeto exclusivo.

Para resolver esses dois casos identifiquei o padrão de desenvolvimento Cópia Exclusiva de Coleção. A idéia é obter a exclusividade da lista somente para fazer uma cópia dos itens em outra coleção e então poder iterar sobre esta cópia sem muitas preocupações. Abaixo mostro um exemplo de uma classe Armario que possui muitas Coisas. Se alguma coisa for lixo, ela deve ser removida na execução do método removeLixo.

public class Armario {
private List coisas = new LinkedList();
public void removeLixo() {
List copia = lista();
for (Coisa coisa : copia) {
if (coisa.isLixo())
remove(coisa);
}
}
public List lista() {
List copia = null;
synchronized (coisas) {
copia = new ArrayList(coisas.size());
copia.addAll(coisas);
}
return copia;
}
public void adiciona(Coisa coisa) {
synchronized (coisas) {
coisas.add(coisa);
}
}
public void remove(Coisa coisa) {
synchronized (coisas) {
coisas.remove(coisa);
}
}
}

Com isso o tempo de exclusividade fica restrito ao tempo da cópia para a outra coleção, que ocorre no método lista. Dessa forma temos uma folga no tempo de iteração total e a possibilidade de rearranjar a coleção, adicionando ou removendo itens a ela. É importante ressaltar que o melhor jeito de evitar o calafrio de receber um java.util.ConcurrentModificationException é evitar a utilização de objetos compartilhados entre threads. Quando isso não é possível, o jeito é utilizar um padrão como Cópia Exclusiva de Coleção.

terça-feira, 16 de setembro de 2008

On-demand javascript com PHP

A técnica de script on-demand serve para compartilhar dados entre domínios. A idéia dela é driblar as restrições da classe XMLHttpRequest, que só permite requisições no mesmo domínio, e a restrição do iframe que no máximo permite comunicação entre sub-domínios de um mesmo domínio.

Basicamente consiste em adicionar um script à página quando uma informação é necessária. Esse script proverá a informação necessária chamando uma função de callback ao solicitador. Uma explicação mais detalhada sobre a técnica pode ser encontrada em On-Demand_Javascript - AjaxPatterns.

Com PHP fica ainda mais fácil desenvolver o script provedor de dados. Digamos por exemplo que você queira obter a lista de livros de uma biblioteca, seu javascript ficaria assim:
var Livros = {
listar:function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = 'listaDeLivros.php?callback=Livros.onload_listar';
document.getElementsByTagName('head')[0].appendChild(s);
}
onload_listar:function(html) {
document.getElementsByTagName('lista').innerHTML = html;
}
}

A função listar insere um javascript à página informando a ele a função javascript de callback. No PHP informaremos o html gerado chamando a função enviada no parâmetro callback. Mais ou menos como mostrado abaixo:
<?php echo $_REQUEST['callback']; ?>("<h1>Lista de livros</h1>");

Mas é claro que normalmente o html gerado não será de apenas uma linha. Além disso ele poderá ter diversos dados com aspas e outros caracteres que poderiam causar erros de javascript. Ficar se preocupando com esse tipo de coisa na diagramação do html é trabalhoso e passível de muitos erros.

Para evitar isso podemos utilizar a função ob_start, que faz com que tudo o que for escrito no output seja guardado em buffer e ao final remetido a uma função de callback do PHP. Nesta função então pode-se tratar o html e enfim escrever no real output para o cliente. Veja como ficaria:
<?php 
function captura_buffer($html) {
$html = addslashes($html);
$html = str_replace("\n", '\n', $html);
$html = str_replace("\r", '\r', $html);
return $_REQUEST['callback'] . '("' . $html . '");';
}
ob_start('captura_buffer');

$livros = funcao_ficticia_para_obter_livros();

foreach($livros as $livro) {
?>

<p>
<?php echo $livro->id ?> -
<b><?php echo $livro->titulo ?></b> -
<?php echo $livro->autor ?>
</p>

<?php } ?>

quarta-feira, 3 de setembro de 2008

KeyDown e KeyUp para applets java

Um grande problema da interface KeyListener do Java é não possuir uma maneira de saber se uma ou mais teclas estão pressionadas ao mesmo tempo, e quando elas realmente são soltas. Quando você mantém uma tecla pressionada os eventos keyPressed e keyReleased são ativados consecutivamente, e se você pressiona e segura outra tecla, o keyReleased da tecla anterior para de ser notificado, seguido por uma notificação consecutiva do keyPressed e keyReleased da nova tecla. Isso é facilmente demonstrado no applet abaixo:
public class ComProblema extends Applet implements KeyListener {
    @Override
    public void init() {
        addKeyListener(this);
    }
    public void keyPressed(KeyEvent e) {
        System.out.println("Pressionou: " + e.getKeyCode());
    }
    public void keyReleased(KeyEvent e) {
        System.out.println("Soltou: " + e.getKeyCode());
    }
    public void keyTyped(KeyEvent e) {}
}
Ao executarmos esse applet clicando e segurando a tecla "a" por alguns segundos, depois apertar e segurar a "w" por outros tantos e ao final soltar as duas, teremos o seguinte resultado:
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 65
Soltou: 87
Encontrei em diversos fóruns pessoas com o mesmo problema e nenhuma solução. Então, para resolvê-lo implementei uma espécie de KeyAdapter para facilitar a notificação de quando uma tecla é apertada e quando ela é solta. O uso de exemplo dela ficaria como o mostrado abaixo:
public class SemProblema extends Applet {
    @Override
    public void init() {
        super.init();
        addKeyListener(new CumulativeKeyAdapterImpl());
    }
}
class CumulativeKeyAdapterImpl extends AcumuladorKeyAdapter {
    @Override
    public void keyDown(KeyEvent e) {
        System.out.println("Down: " + e.getKeyCode());
    }
    @Override
    public void keyUp(KeyEvent e) {
        System.out.println("Up: " + e.getKeyCode());
    }
}
O resultado fazendo a mesma combinação de teclas apertadas no teste anterior neste outro applet, seria como o mostrado a seguir:
Down: 65
Down: 87
Up: 65
Up: 87
Você também pode ver outros exemplos de applets utilizando o AcumuladorKeyAdapter aqui. Para utilizar esse recurso basta baixar os fontes e binários da timotta-api e colocar o timotta-api-applet.jar no atributo archive da sua tag applet, como demonstrado no exemplo abaixo:
<applet code="br.com.timotta.applet.MostraTeclas" 
    archive="timotta-api-applet-exemplos.jar,timotta-api-applet.jar"
    width=256 height=256 >
</applet>