Pourquoi le SimpleDateFormat de Java n'est-il pas thread-safe? [dupliquer]
cette question a déjà une réponse ici:
s'il vous plaît dire avec un exemple de code pourquoi est SimpleDateFormat pas threadsafe. Quel est le problème dans cette classe? est le problème avec le format fonction de SimpleDateFormat ? Veuillez donner un code qui démontre ce défaut en classe.
FastDateFormat est thread-safe. Pourquoi? Quelle est la différence entre le SimpleDateFormat et le FastDateFormat?
Veuillez expliquer avec un code qui démontre cette question?
9 réponses
SimpleDateFormat
stocke les résultats intermédiaires dans les champs d'instance. Ainsi, si une instance est utilisée par deux threads, ils peuvent affecter les résultats de l'autre.
en regardant le code source révèle qu'il y a un champ d'instance Calendar
, qui est utilisé par les opérations sur DateFormat
/ SimpleDateFormat
1519110920"
par exemple parse(..)
appelle calendar.clear()
d'abord puis calendar.add(..)
. Si un autre fil invoque parse(..)
avant l'achèvement de la première invocation, il effacera le calendrier, mais l'autre invocation s'attendra à ce qu'il soit rempli avec les résultats intermédiaires du calcul.
une façon de réutiliser les formats de date sans fil trading-la sécurité est de les mettre dans un ThreadLocal
- certaines bibliothèques font cela. C'est si vous avez besoin d'utiliser le même format plusieurs fois dans un seul thread. Mais dans le cas où vous utilisez un conteneur de servlet (qui a un pool de thread), rappelez-vous de nettoyer le fil-local après avoir terminé.
pour être honnête, je ne comprends pas pourquoi ils ont besoin du champ d'instance, mais c'est comme ça. Vous pouvez également utiliser Joda-time DateTimeFormat
qui est threadsafe.
SimpleDateFormat
est une classe de béton pour le formatage et l'analyse des dates d'une manière sensible à la localisation.
de la JavaDoc
,
mais les formats de Date sont non synchronisé . Il est recommandé de créer séparer les instances de format pour chaque thread. Si plusieurs threads d'accès un format simultané,
it must be synchronized externally
.
pour rendre le SimpleDateFormat classe thread-safe, regardez le approches suivantes :
- créez une nouvelle instance SimpleDateFormat chaque fois que vous avez besoin d'en utiliser une. Bien qu'il s'agisse d'une approche sans fil, c'est l'approche la plus lente possible.
- utiliser la synchronisation. C'est une mauvaise idée parce que vous ne devriez jamais étouffer-pointer vos threads sur un serveur.
- Use un ThreadLocal. C'est l'approche la plus rapide des 3 (voir http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).
DateTimeFormatter
en Java 8 est immuable et thread-safe alternative à 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<SimpleDateFormat> localSimpleDateFormat;
public SimpleDateFormatThreadSafe() {
super();
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat();
}
};
}
public SimpleDateFormatThreadSafe(final String pattern) {
super(pattern);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
}
public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) {
super(pattern, formatSymbols);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern, formatSymbols);
}
};
}
public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) {
super(pattern, locale);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
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);
}
}
Voici l'exemple qui résulte en une erreur étrange. Même Google ne donne aucun résultat:
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 ");
}
}
et résultat:
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.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)
voici un exemple de code qui prouve la faute dans la classe. J'ai vérifié: le problème se produit lors de l'utilisation de parse et aussi lorsque vous utilisez seulement le format.
voici un exemple qui définit un objet SimpleDateFormat comme un champ statique. Lorsque deux ou plusieurs threads accèdent à "someMethod" simultanément avec des dates différentes, ils peuvent brouiller les résultats de l'autre.
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);
}
}
vous pouvez créer un service comme ci-dessous et utiliser jmeter pour simuler les utilisateurs concurrents en utilisant le même formatage objet SimpleDateFormat différentes dates et leurs résultats seront foiré.
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();
}
}
le code et le script jmeter peuvent être téléchargés ici .
si vous voulez utiliser le même format de date parmi plusieurs threads, déclarez-le comme statique et synchronisez sur la variable d'instance en l'utilisant...
static private SimpleDateFormat sdf = new SimpleDateFormat("....");
synchronized(sdf)
{
// use the instance here to format a date
}
// The above makes it thread safe