Por que o SimpleDateFormat do Java não é thread-safe?

Por favor, diga com um exemplo de código porque SimpleDateFormat não é thread-safe. Qual é o problema nesta aula? O problema com a function de formatação do SimpleDateFormat ? Por favor, indique um código que demonstre esta falha na aula.

FastDateFormat é threadsafe. Por quê? Qual é a diferença entre o SimpleDateFormat e o FastDateFormat?

Por favor, explique com um código que demonstra este problema?

SimpleDateFormat armazena resultados intermediários em campos de instância. Então, se uma instância é usada por dois tópicos, eles podem atrapalhar os resultados uns dos outros.

Observar o código-fonte revela que há um campo de instância do Calendar , que é usado pelas operações em DateFormat / SimpleDateFormat

Por exemplo, parse(..) chama calendar.clear() inicialmente e depois calendar.add(..) . Se outro encadeamento chamar parse(..) antes da conclusão da primeira chamada, ele limpará o calendar, mas a outra chamada esperará que ele seja preenchido com resultados intermediários do cálculo.

Uma maneira de reutilizar os formatos de data sem negociar segurança de thread é colocá-los em um ThreadLocal – algumas bibliotecas fazem isso. Isso se você precisar usar o mesmo formato várias vezes em um thread. Mas, caso você esteja usando um contêiner de servlet (que possui um conjunto de encadeamentos), lembre-se de limpar o encadeamento local depois de concluir.

Para ser honesto, não entendo por que eles precisam do campo da instância, mas é assim que é. Você também pode usar o DateTimeFormat joda-time, que é threadsafe.

SimpleDateFormat é uma class concreta para formatar e analisar datas de uma maneira sensível ao código do idioma.

Do JavaDoc ,

Mas os formatos de data não estão sincronizados . Recomenda-se criar instâncias de formato separadas para cada thread. Se vários segmentos acessarem um formato simultaneamente, it must be synchronized externally .

Para tornar a class SimpleDateFormat segura para threads, observe as seguintes abordagens :

  • Crie uma nova instância SimpleDateFormat sempre que precisar usar uma. Embora isso seja thread-safe, é a abordagem mais lenta possível.
  • Use a synchronization. Esta é uma má ideia, porque você nunca deve bloquear seus threads em um servidor.
  • Use um ThreadLocal. Esta é a abordagem mais rápida dos 3 (consulte http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).

DateTimeFormatter no Java 8 é uma alternativa imutável e segura para o SimpleDateFormat .

ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe

 package com.foocoders.text; import java.text.AttributedCharacterIterator; import java.text.DateFormatSymbols; import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; public class SimpleDateFormatThreadSafe extends SimpleDateFormat { private static final long serialVersionUID = 5448371898056188202L; ThreadLocal localSimpleDateFormat; public SimpleDateFormatThreadSafe() { super(); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; } public SimpleDateFormatThreadSafe(final String pattern) { super(pattern); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern); } }; } public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) { super(pattern, formatSymbols); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern, formatSymbols); } }; } public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) { super(pattern, locale); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern, locale); } }; } public Object parseObject(String source) throws ParseException { return localSimpleDateFormat.get().parseObject(source); } public String toString() { return localSimpleDateFormat.get().toString(); } public Date parse(String source) throws ParseException { return localSimpleDateFormat.get().parse(source); } public Object parseObject(String source, ParsePosition pos) { return localSimpleDateFormat.get().parseObject(source, pos); } public void setCalendar(Calendar newCalendar) { localSimpleDateFormat.get().setCalendar(newCalendar); } public Calendar getCalendar() { return localSimpleDateFormat.get().getCalendar(); } public void setNumberFormat(NumberFormat newNumberFormat) { localSimpleDateFormat.get().setNumberFormat(newNumberFormat); } public NumberFormat getNumberFormat() { return localSimpleDateFormat.get().getNumberFormat(); } public void setTimeZone(TimeZone zone) { localSimpleDateFormat.get().setTimeZone(zone); } public TimeZone getTimeZone() { return localSimpleDateFormat.get().getTimeZone(); } public void setLenient(boolean lenient) { localSimpleDateFormat.get().setLenient(lenient); } public boolean isLenient() { return localSimpleDateFormat.get().isLenient(); } public void set2DigitYearStart(Date startDate) { localSimpleDateFormat.get().set2DigitYearStart(startDate); } public Date get2DigitYearStart() { return localSimpleDateFormat.get().get2DigitYearStart(); } public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { return localSimpleDateFormat.get().format(date, toAppendTo, pos); } public AttributedCharacterIterator formatToCharacterIterator(Object obj) { return localSimpleDateFormat.get().formatToCharacterIterator(obj); } public Date parse(String text, ParsePosition pos) { return localSimpleDateFormat.get().parse(text, pos); } public String toPattern() { return localSimpleDateFormat.get().toPattern(); } public String toLocalizedPattern() { return localSimpleDateFormat.get().toLocalizedPattern(); } public void applyPattern(String pattern) { localSimpleDateFormat.get().applyPattern(pattern); } public void applyLocalizedPattern(String pattern) { localSimpleDateFormat.get().applyLocalizedPattern(pattern); } public DateFormatSymbols getDateFormatSymbols() { return localSimpleDateFormat.get().getDateFormatSymbols(); } public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) { localSimpleDateFormat.get().setDateFormatSymbols(newFormatSymbols); } public Object clone() { return localSimpleDateFormat.get().clone(); } public int hashCode() { return localSimpleDateFormat.get().hashCode(); } public boolean equals(Object obj) { return localSimpleDateFormat.get().equals(obj); } } 

https://gist.github.com/pablomoretti/9748230

Versão 3.2 do commons-lang terá class FastDateParser que é um substituto thread-safe de SimpleDateFormat para o calendar gregoriano. Veja LANG-909 para mais informações.

Aqui está o exemplo que resulta em um erro estranho. Mesmo o Google não dá resultados:

 public class ExampleClass { private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)"); private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy"); public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(100); while (true) { executor.submit(new Runnable() { @Override public void run() { workConcurrently(); } }); } } public static void workConcurrently() { Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015"); Timestamp startAdvDate = null; try { if (matcher.find()) { String dateCreate = matcher.group(1); startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime()); } } catch (Throwable th) { th.printStackTrace(); } System.out.print("OK "); } } 

E resultado:

 OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK java.lang.NumberFormatException: For input string: ".201519E.2015192E2" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2056) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at com.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.java:37) at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.java:25) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) 

Aqui está um exemplo de código que comprova a falha na class. Eu verifiquei: o problema ocorre ao usar análise e também quando você está usando apenas o formato.

Aqui está um exemplo define um object SimpleDateFormat como um campo estático. Quando dois ou mais threads acessam “someMethod” simultaneamente com datas diferentes, eles podem mexer nos resultados uns dos outros.

  public class SimpleDateFormatExample { private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); public String someMethod(Date date) { return simpleFormat.format(date); } } 

Você pode criar um serviço como abaixo e usar o jmeter para simular usuários simultâneos usando o mesmo object SimpleDateFormat formatando datas diferentes e seus resultados ficarão confusos.

 public class FormattedTimeHandler extends AbstractHandler { private static final String OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; private static final String INPUT_TIME_FORMAT = "yyyy-MM-ddHH:mm:ss"; private static final SimpleDateFormat simpleFormat = new SimpleDateFormat(OUTPUT_TIME_FORMAT); // apache commons lang3 FastDateFormat is threadsafe private static final FastDateFormat fastFormat = FastDateFormat.getInstance(OUTPUT_TIME_FORMAT); public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); baseRequest.setHandled(true); final String inputTime = request.getParameter("time"); Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate(); final String method = request.getParameter("method"); if ("SimpleDateFormat".equalsIgnoreCase(method)) { // use SimpleDateFormat as a static constant field, not thread safe response.getWriter().println(simpleFormat.format(date)); } else if ("FastDateFormat".equalsIgnoreCase(method)) { // use apache commons lang3 FastDateFormat, thread safe response.getWriter().println(fastFormat.format(date)); } else { // create new SimpleDateFormat instance when formatting date, thread safe response.getWriter().println(new SimpleDateFormat(OUTPUT_TIME_FORMAT).format(date)); } } public static void main(String[] args) throws Exception { // embedded jetty configuration, running on port 8090. change it as needed. Server server = new Server(8090); server.setHandler(new FormattedTimeHandler()); server.start(); server.join(); } 

}

O código e o script jmeter podem ser baixados aqui .

Se você quiser usar o mesmo formato de data entre vários segmentos, declare-o como estático e sincronize na variável de instância ao usá-lo …

 static private SimpleDateFormat sdf = new SimpleDateFormat("...."); synchronized(sdf) { // use the instance here to format a date } // The above makes it thread safe