Der Javadoc für SimpleDateFormat gibt an, dass SimpleDateFormat nicht synchronisiert ist.
Msgstr "Datumsformate werden nicht synchronisiert. Es wird empfohlen, für jeden Thread separate Formatinstanzen zu erstellen. Wenn mehrere Threads gleichzeitig auf ein Format Zugreifen, muss es extern synchronisiert werden."
Was ist der beste Ansatz, um eine Instanz von SimpleDateFormat in einer Umgebung mit mehreren Threads zu verwenden? Hier sind ein paar Optionen, an die ich gedacht habe, ich habe in der Vergangenheit die Optionen 1 und 2 verwendet, aber ich bin neugierig, ob es bessere Alternativen gibt oder welche dieser Optionen die beste Leistung und Parallelität bietet.
Option 1: Erstellen Sie bei Bedarf lokale Instanzen
public String formatDate(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
}
Option 2: Erstellen Sie eine Instanz von SimpleDateFormat als Klassenvariable, synchronisieren Sie jedoch den Zugriff darauf.
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
synchronized(sdf) {
return sdf.format(d);
}
}
Option 3: Erstellen Sie ein ThreadLocal, um für jeden Thread eine andere Instanz von SimpleDateFormat zu speichern.
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);
}
Das Erstellen von SimpleDateFormat ist teuer . Verwenden Sie das nicht, es sei denn, es wird selten gemacht.
OK, wenn Sie mit etwas Blockieren leben können. Verwenden Sie dies, wenn formatDate () nicht häufig verwendet wird.
Schnellste Option, WENN Sie Threads wiederverwenden ( Threadpool ). Verwendet mehr Arbeitsspeicher als 2. und hat einen höheren Systemstartaufwand.
Für Anwendungen sind sowohl 2. als auch 3. realisierbare Optionen. Welches für Ihren Fall am besten ist, hängt von Ihrem Anwendungsfall ab. Vorsicht vor vorzeitiger Optimierung. Tun Sie es nur, wenn Sie glauben, dass dies ein Problem ist.
Für Bibliotheken, die von Drittanbietern verwendet werden, würde ich Option 3 verwenden.
Die andere Option ist Commons Lang FastDateFormat. Sie können sie jedoch nur zur Datumsformatierung und nicht zum Analysieren verwenden.
Im Gegensatz zu Joda kann es als Drop-In-Ersatz für die Formatierung fungieren . (Update: Seit v3.3.2 kann FastDateFormat einen FastDateParser erzeugen, der ein Drop-in-Thread-sicherer Ersatz für SimpleDateFormat ist)
Wenn Sie Java 8 verwenden, möchten Sie möglicherweise Java.time.format.DateTimeFormatter
verwenden:
Diese Klasse ist unveränderlich und threadsicher.
z.B.:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new Java.util.Date().toInstant()
.atZone(ZoneId.systemDefault())
.format(formatter);
Commons Lang 3.x hat jetzt FastDateParser sowie FastDateFormat. Es ist Thread-sicher und schneller als SimpleDateFormat. Es verwendet auch dieselben Format-/Analyse-Musterspezifikationen wie SimpleDateFormat.
Verwenden Sie SimpleDateFormat nicht, sondern DateTimeFormatter von joda-time. Im Parsing-Bereich ist es etwas strenger, was SimpleDateFormat nicht ersetzen kann, aber joda-time ist in Bezug auf Sicherheit und Leistung wesentlich freundlicher.
Ich würde sagen, eine einfache Wrapper-Klasse für SimpleDateFormat erstellen, die den Zugriff auf parse () und format () synchronisiert und als Drop-In-Ersatz verwendet werden kann. Idiotensicherer als Ihre Option # 2, weniger umständlich als Ihre Option # 3.
Es scheint so, als ob SimpleDateFormat nicht synchronisiert wurde, war eine schlechte Entwurfsentscheidung seitens der Java-API-Designer. Ich bezweifle, dass jemand erwartet, dass format () und parse () synchronisiert werden müssen.
Ich habe dies gerade mit Option 3 implementiert, aber ein paar Codeänderungen vorgenommen:
Möglicherweise möchten Sie Gebietsschema und Zeitzone festlegen, es sei denn, Sie möchten wirklich die Standardeinstellungen (Standardeinstellungen sind mit Java sehr fehleranfällig).
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);
}
Eine weitere Option besteht darin, Instanzen in einer Thread-sicheren Warteschlange zu behalten:
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;
}
Die Größe von dateFormatQueue sollte in etwa der geschätzten Anzahl von Threads entsprechen, die diese Funktion routinemäßig gleichzeitig aufrufen können .. Im schlimmsten Fall, wenn mehr Threads als diese Anzahl tatsächlich alle Instanzen gleichzeitig verwenden, werden einige SimpleDateFormat-Instanzen verwendet erstellt werden, das nicht an dateFormatQueue zurückgegeben werden kann, da es voll ist. Dadurch wird kein Fehler generiert, sondern es besteht die Strafe, dass einige SimpleDateFormat erstellt werden, die nur einmal verwendet werden.
Stellen Sie sich vor, Ihre Anwendung hat einen Thread. Warum sollten Sie dann den Zugriff auf die Variable SimpleDateFormat synchronisieren?