
Clique aqui para ler essa revista em PDF.
Programando com Pools
Explore várias facetas de uma poderosa técnica de programação
Pooling, Jakarta Commons Pools, DBCP, DataSources, Micro-pools...
A programação às vezes parece ser um campo tomado por uma enorme e confusa variedade de técnicas. Mas isso só é assim na superfície, devido à grande especialização das ferramentas disponíveis. Fazendo um esforço de síntese, veremos que existe um número relativamente pequeno de fundamentos que embasam toda a programação.
Por exemplo, ordenação: é uma operação tão importante, que até deu nome ao computador em Espanhol (ordenador) e Francês (ordinateur). Obras clássicas de programação dedicaram tomos inteiros ao assunto. Revoluções foram geradas por aperfeiçoamentos em algoritmos de ordenação, por exemplo, os modernos jogos de ação 3D são resultado direto de algoritmos que ordenam os objetos geométricos de uma cena de forma a reduzir o esforço para renderizá-los[1].
Um outro coringa da programação é a idéia de pooling. Conceitualmente muito simples, “pooling” é um nome mais bonito para “reciclagem”. E da mesma forma como reciclar lixo é benéfico para o meio ambiente, reciclar dados ou atividades pode ser muito bom para o desempenho de programas. Há muitas variações sobre o tema de pooling, e neste artigo vamos explorar algumas delas, sempre de forma aplicada. Começaremos mostrando o Jakarta Common Pool, uma biblioteca que permite construir pools genéricos. Veremos também o Jakarta DBCP, complemento do Commons Pool que implementa pools de conexões JDBC; e os pools de threads oferecidos pela java.util.concurrent. Investigaremos questões como pooling versus garbage collection, e variantes da técnica.
Criando e usando pools
Vamos começar pelos fundamentos: como criar e utilizar um pool. A técnica de pooling é poderosa, mas também possui custos tanto em complexidade do código quanto em desempenho. Sem pooling, é fácil criar um objeto que necessitamos para realizar algum trabalho:
StringBuilder sb = new StringBuilder();
// Utiliza sb, por exemplo para criar uma String complexa
A técnica de pooling consiste em evitar tais criações, preferindo reusar os mesmos objetos em vários lugares onde forem necessários. Mas o pooling sempre complica um pouco o código. A alocação é substituída por uma invocação a algum método do pool que retorna um objeto – até aí, tudo igual. Mas depois de usar o objeto, você tem que devolvê-lo ao pool:
try {
StringBuilder sb = StringBuilderPool.create();
// Utiliza sb, por exemplo para criar uma String complexa
} finally {
StringBuilderPool.free(sb);
}
Além de exigir código extra para devolver o objeto ao pool, esta técnica cria o perigo de leaks, o que exige ainda mais código – um bloco try/catch – para garantir que a desalocação sempre será feita, mesmo se ocorrer alguma exceção.
Isso sem falar no trabalho de implementar o pool propriamente dito – no exemplo, uma classe customizada StringBuilderPool. Veja um exemplo de implementação simples na Listagem 1.
Listagem 1. Pool simples para objetos StringBuilder.
public class StringBuilderPool {
private static ArrayDeque<StringBuffer> pool = new ArrayDeque<StringBuffer>();
public static StringBuffer create () {
StringBuffer obj = pool.pollFirst();
return obj == null ? new StringBuffer() : obj;
}
public static void free (StringBuffer sb) {
sb.setLength(0); // “Limpeza” do objeto: volta ao estado original de criação
pool.addLast(sb);
}
}
Nossa classe StringBuilderPool pode parecer simples, mas esta simplicidade oculta algumas limitações. Em primeiro lugar, não há nenhuma sincronização: o pool só pode ser utilizado por um thread de cada vez. Se você usar métodos synchronized, o custo disso será maior do que o custo de alocar um novo objeto[2]. Isso poderia ser contornado com técnicas de programação concorrente mais elaboradas, mas o código resultante seria maior e mais complexo.
Este pool tem um comportamento pouco inteligente: começa vazio e, sempre que não possui o objeto solicitado, cria um novo. Ou seja, cresce de forma indefinida conforme a demanda, e nunca diminui de tamanho. Um pool mais robusto deveria permitir a especificação de um tamanho-limite, e deveria desalocar objetos quando muito tempo passa sem que estes sejam utilizados.
Finalmente, este pool serve para um único tipo de objeto, StringBuilder ...