Funciona na minha máquina: SimpleDateFormat

Um erro muito comum e bastante perigoso é utilizar o SimpleDateFormat ou NumberFormat como atributo estático de classe. Já vi dezenas de códigos assim em sistemas em produção:

public static final SimpleDateFormat FORMATTER = new SimpleDateFormat(
			"dd/MM/yyyy");

Qual é o perigo disso? Essas classes não são thread-safe. O que significa isso? Significa que você não pode compartilhar o mesmo objeto (FORMATTER) em partes diferentes de código que podem ser concorrentes. Isso é muito comum em sistemas Web. Se você já fez isso em uma Servlet ou ManagedBean, saiba que o seu código tem grandes chances de não funcionar conforme o esperado. Esse é o famoso: "Funciona na minha máquina!". Para demonstrar esse problema vamos utilizar o seguinte código:

package br.com.thiagovespa.sample.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatThreadUnsafe implements Runnable {
	public static final SimpleDateFormat FORMATTER = new SimpleDateFormat(
			"dd/MM/yyyy");
	public static final String[] toParse = { "21/12/2012", "04/09/1982",
			"01/01/2013", "25/12/2012" };

	public static void main(String[] args) {

		final int threadPoolSize = toParse.length;
		ExecutorService execService = Executors
				.newFixedThreadPool(threadPoolSize);
		for (int i = 0; i < threadPoolSize; i++) {
			execService.submit(new SimpleDateFormatThreadUnsafe());
		}
	}

	@Override
	public void run() {
		for (;;) {
			try {
				String dateToParse = toParse[new Random().nextInt(3)];
				Date parsedDate = FORMATTER.parse(dateToParse);
				String parsedDateToString = FORMATTER.format(parsedDate);
				if (!dateToParse.equals(parsedDateToString)) {
					System.out.println("Erro. Esperado: "
							+ dateToParse + ". Obtido: " + parsedDateToString);
				}
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}

	}
}

O problema se encontra na linha 11. Estamos criando 4 threads concorrentes que vão converter (parse) de um valor do tipo String para Date (linha 31) e transformar o mesmo valor (format) de Date para String (linha 32). Na teoria o valor da linha 30 deveria ser o mesmo da linha 32, mas como o objeto SimpleDateFormat não é thread-safe temos a seguinte saída:

Erro. Esperado: 01/01/2013. Obtido: 04/01/2013
Erro. Esperado: 01/01/2013. Obtido: 04/01/2013
Erro. Esperado: 04/09/1982. Obtido: 21/12/2012
Erro. Esperado: 04/09/1982. Obtido: 04/09/2013
Erro. Esperado: 04/09/1982. Obtido: 04/12/2012
Erro. Esperado: 21/12/2012. Obtido: 04/12/2012
Erro. Esperado: 01/01/2013. Obtido: 13/01/2013
Erro. Esperado: 04/09/1982. Obtido: 21/12/0001
Erro. Esperado: 21/12/2012. Obtido: 21/12/0001
Erro. Esperado: 04/09/1982. Obtido: 21/09/1982
Erro. Esperado: 01/01/2013. Obtido: 01/12/2011
Erro. Esperado: 21/12/2012. Obtido: 01/12/2011
Erro. Esperado: 04/09/1982. Obtido: 01/01/2013
Erro. Esperado: 01/01/2013. Obtido: 21/12/2012
Erro. Esperado: 01/01/2013. Obtido: 01/01/1982
Erro. Esperado: 01/01/2013. Obtido: 04/09/1982

O pior é que não é lançada nenhuma exception. O valor fica diferente do esperado. Normalmente, esse problema só vai aparecer quando o sistema estiver em produção com vários usuários acessando (várias threads). A solução é sincronizar ou instanciar um objeto para cada conversão ao invés de tentar reutilizá-lo. Portanto, antes de dizer: "Funciona na minha máquina!", pergunte antes se a classe que você está utilizando é thread-safe.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6