terça-feira, 18 de maio de 2010

Formatação eficiente no Java

Uma das maiores fontes de erros e ineficiência que costumo encontrar em sistemas Web escritos em Java é o mau uso das classes de formatação da package java.text.

A documentação avisa que as classes não podem ser usadas concorrentemente. Mas quem lê a documentação? Outro problema é a instanciação excessiva. Tenho a impressão de que ela não ocorre por precaução, mas por indiferença.

Em sistemas Web, principalmente os de alto tráfego, alguns detalhes podem fazer muita diferença. Se instanciar um objeto não custa muito, removê-lo é muito demorado. Num teste simples, descobri que reutilizar uma instância é quatro vezes mais rápido que criar uma nova. E uma instância reutilizada não precisa ser recolhida pelo coletor de lixo.

A solução está na classe ThreadLocal do pacote java.lang. Nem é preciso importar a package! Para a formatação, costumo criar uma classe utilitária da seguinte forma:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Formato {

private static final ThreadLocal formatoData =
new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
return df;
}
};

public static SimpleDateFormat getFormatoData() {
return formatoData.get();
}

public static Date data(String value) {
return getFormatoData().parse(value);
}

public static String data(Date value) {
return getFormatoData().format(value);
}

}

Uso nomes bem concisos para os métodos para tornar o código mais simples. O significado fica bastante claro pelo uso:

String hoje=Formato.data(new Date());

Assim, fica garantido que para cada linha de execução haverá apenas uma instância para cada formatação. E todas as operações são feitas usando essa mesma instância. Os servidores de aplicação costumam reaproveitar as threads e então, uma vez criado, cada objeto terá uma longa vida. Além disso, não tem sentido ter muitas linhas de execução por processador. Tipicamente, usam-se 4 ou 8. Se houver mais que isso, o processador vai ficar mais tempo trocando de contexto que fazendo algo útil.

Para executar outras transformações, basta adicionar subclasses de ThreadLocal e os respectivos métodos, conforme o exemplo. Será necessária uma instância para cada tipo de conversão.

5 comentários:

  1. Meu, fiquei boiando. Ou boiano como diz a Lady Kate.
    São duas funções internas que definem uma variável correto ?
    E o @override ?
    E ThreadLocal é algo que existe por default no ar que se respira no programa ?
    Outra(s) definição(ões) para ler.
    Entendi que como fica em uma classe estática final, então está "sempre lá" (lá aonde ?).
    Prático saber duas línguas né? Não dá colisão de nomes.

    ResponderExcluir
  2. Fizeste que eu notasse que tinha esquecido o método getFormatoData(). Que falha!

    Bom, acho que a resposta merece um post detalhado, mas vou adiantar o serviço com um resumo.

    A classe ThreadLocal representa um valor único dentro de uma linha de execução (Thread para os íntimos). A classe usa o método initialValue() para instanciar um objeto. Eu sobrescrevi o initialValue() para que retornasse uma instância de SimpleDateFormat.

    Cada vez que alguém invocar o get(), a ThreadLocal vai verificar se já existe uma instância para a Thread corrente e vai criar uma se não existir.

    A anotação @Override indica para o compilador que aquele método está sendo sobrescrito. Isto é, estou criando uma versão nova do método declarado em ThreadLocal. Se eu errar o nome do método, o compilador vai reclamar que eu não estou sobrescrevendo nada. É um seguro contra erros de digitação.

    Os métodos são estáticos porque meu objetivo é justamente ter somente uma instância de cada coisa por linha de execução.

    ResponderExcluir
  3. Acho que isso é muita canja prá pouca galinha! Você não poderia simplesmente ter um objeto SimpleDateFormat singleton???

    ResponderExcluir
  4. Não, Spit-fire, o SimpleDateFormat não é Thread-safe. Não posso compartilhar uma instância dela entre duas linhas de execução. Por isso apresentei essa solução; ela é a mais econômica possível que não provoca erros.

    ResponderExcluir
  5. Comentando em post antigo por causa de algo interessante que aprendi recentemente: ThreadLocals podem causar class loader leaks (gerando estouro de PermGen) quando se não forem liberados depois do uso nem no undeploy.

    http://stackoverflow.com/questions/17968803/threadlocal-memory-leak

    ResponderExcluir