Sincronizando acesso a SimpleDateFormat

90

O javadoc para SimpleDateFormat afirma que SimpleDateFormat não está sincronizado.

"Os formatos de data não são sincronizados. É recomendado criar instâncias de formato separadas para cada thread. Se vários threads acessarem um formato simultaneamente, ele deve ser sincronizado externamente."

Mas qual é a melhor abordagem para usar uma instância de SimpleDateFormat em um ambiente multiencadeado. Aqui estão algumas opções que pensei, usei as opções 1 e 2 no passado, mas estou curioso para saber se há alternativas melhores ou qual dessas opções ofereceria o melhor desempenho e simultaneidade.

Opção 1: criar instâncias locais quando necessário

public String formatDate(Date d) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(d);
}

Opção 2: crie uma instância de SimpleDateFormat como uma variável de classe, mas sincronize o acesso a ela.

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
    synchronized(sdf) {
        return sdf.format(d);
    }
}

Opção 3: Crie um ThreadLocal para armazenar uma instância diferente de SimpleDateFormat para cada thread.

private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
    SimpleDateFormat sdf = tl.get();
    if(sdf == null) {
        sdf = new SimpleDateFormat("yyyy-MM-hh");
        tl.set(sdf);
    }
    return sdf.format(d);
}
3urdoch
fonte
10
1 por levantar esta questão. Muitas pessoas pensam que SimpleDateFormat é thread-safe (vejo suposições em todos os lugares).
Adam Gent,
Para obter mais informações sobre a abordagem ThreadLocal, consulte: javaspecialists.eu/archive/Issue172.html
miner49r
E para saber por quê, veja esta pergunta: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch Você ignorou por engano a palavra-chave 'estática' na Opção 2?
M Faisal Hameed

Respostas:

43
  1. Criar SimpleDateFormat é caro . Não use isso, a menos que seja feito raramente.

  2. Tudo bem se você conseguir viver com um pouco de bloqueio. Use se formatDate () não for muito usado.

  3. A opção mais rápida SE você reutilizar threads ( pool de threads ). Usa mais memória do que 2. e tem maior sobrecarga de inicialização.

Para aplicações, 2. e 3. são opções viáveis. O que é melhor para o seu caso depende do seu caso de uso. Cuidado com a otimização prematura. Só faça isso se você acreditar que isso é um problema.

Para bibliotecas que seriam usadas por terceiros, eu usaria a opção 3.

Peter Knego
fonte
Se usarmos a Opção 2 e declararmos SimpleDateFormatcomo uma variável de instância, podemos usar synchronized blockpara torná-la segura para thread. Mas o sonar mostra o aviso squid-AS2885 . Existe alguma maneira de resolver o problema do sonar?
M Faisal Hameed
24

A outra opção é Commons Lang FastDateFormat, mas você só pode usá-lo para formatação de data e não para análise.

Ao contrário do Joda, ele pode funcionar como um substituto imediato para a formatação. (Atualização: desde a v3.3.2, FastDateFormat pode produzir um FastDateParser , que é uma substituição de thread-safe drop-in para SimpleDateFormat)

Adam Gent
fonte
8
Desde Commons Lang 3.2, FastDateFormattem parse()método também
manuna
19

Se você estiver usando o Java 8, convém usar java.time.format.DateTimeFormatter:

Esta classe é imutável e segura para threads.

por exemplo:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);
Paul Vargas
fonte
6

Commons Lang 3.x agora tem FastDateParser, bem como FastDateFormat. É thread-safe e mais rápido do que SimpleDateFormat. Ele também usa as mesmas especificações de padrão de formato / análise como SimpleDateFormat.

Chas
fonte
Está disponível apenas em 3.2+ e não 3.x
Wisteso
4

Não use SimpleDateFormat, use DateTimeFormatter do joda-time. É um pouco mais rígido no lado da análise e, portanto, não é uma queda na substituição de SimpleDateFormat, mas o joda-time é muito mais amigável ao mesmo tempo em termos de segurança e desempenho.

Jed Wesley-Smith
fonte
3

Eu diria, crie uma classe de invólucro simples para SimpleDateFormat que sincronize o acesso a parse () e format () e possa ser usada como um substituto drop-in. Mais infalível do que a opção nº 2, menos complicado do que a opção nº 3.

Parece que tornar SimpleDateFormat dessincronizado foi uma má decisão de design por parte dos designers da API Java; Duvido que alguém espere que format () e parse () precisem ser sincronizados.

Andy
fonte
1

Outra opção é manter as instâncias em uma fila thread-safe:

import java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    String text = fmt.format(date);
    dateFormatQueue.offer(fmt);
    return text;
}
public Date parse(String text) throws ParseException {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    Date date = null;
    try {
        date = fmt.parse(text);
    } finally {
        dateFormatQueue.offer(fmt);
    }
    return date;
}

O tamanho de dateFormatQueue deve ser algo próximo ao número estimado de threads que podem chamar essa função rotineiramente ao mesmo tempo. No pior caso, onde mais encadeamentos do que esse número realmente usam todas as instâncias simultaneamente, algumas instâncias SimpleDateFormat serão criadas, as quais não podem ser retornadas para dateFormatQueue porque está cheio. Isso não gerará um erro, apenas incorrerá na penalidade de criar alguns SimpleDateFormat que são usados ​​apenas uma vez.

SamJ
fonte
1

Acabei de implementar isso com a opção 3, mas fiz algumas alterações no código:

  • ThreadLocal geralmente deve ser estático
  • Parece mais limpo substituir initialValue () em vez de testar if (get () == null)
  • Você pode querer definir o local e o fuso horário, a menos que você realmente queira as configurações padrão (os padrões são muito sujeitos a erros com Java)

    private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
            return sdf;
        }
    };
    public String formatDate(Date d) {
        return tl.get().format(d);
    }
mwk
fonte
0

Imagine que seu aplicativo tenha um thread. Por que você sincronizaria o acesso à variável SimpleDataFormat então?

Vórtice
fonte