„Java Dateformat ist nicht THREAD“ Was bedeutet dies führt zu?
-
26-09-2019 - |
Frage
Jeder Vorsichtsmaßnahmen in Bezug auf Java Date nicht Thread-sicher zu sein, und ich verstehe das Konzept theoretisch.
Aber ich bin nicht in der Lage zu visualisieren, was tatsächlichen Probleme, die wir aufgrund dieser stellen kann. Sprich: Ich habe ein Date Feld in einer Klasse und das gleiche wird in verschiedenen Methoden in der Klasse (Formatierung Daten) in einer Multi-Threaded-Umgebung verwendet.
Wird diese Ursache:
- jede Ausnahme wie Format Ausnahme
- Diskrepanz in Daten
- andere Frage?
Sie bitte auch erklären, warum.
Lösung
Lassen Sie uns versuchen Sie es aus.
Hier ist ein Programm, in dem mehrere Threads eine gemeinsame SimpleDateFormat
verwenden.
Programm :
public static void main(String[] args) throws Exception {
final DateFormat format = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>(){
public Date call() throws Exception {
return format.parse("20101022");
}
};
//pool with 5 threads
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<Date>> results = new ArrayList<Future<Date>>();
//perform 10 date conversions
for(int i = 0 ; i < 10 ; i++){
results.add(exec.submit(task));
}
exec.shutdown();
//look at the results
for(Future<Date> result : results){
System.out.println(result.get());
}
}
Führen Sie diese ein paar Mal und Sie werden sehen:
Ausnahmen :
Hier sind einige Beispiele:
1.
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Long.parseLong(Long.java:431)
at java.lang.Long.parseLong(Long.java:468)
at java.text.DigitList.getLong(DigitList.java:177)
at java.text.DecimalFormat.parse(DecimalFormat.java:1298)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
2.
Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
3.
Caused by: java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
falsche Ergebnisse :
Sat Oct 22 00:00:00 BST 2011
Thu Jan 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Thu Oct 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Die korrekten Ergebnisse :
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Ein weiterer Ansatz, um sicher DateFormats in einer Multi-Threaded-Umgebung zu verwenden ist, eine ThreadLocal
Variable zu verwenden, um das DateFormat
Objekt zu halten, was bedeutet, dass jeder Thread seine eigene Kopie hat und muss nicht für andere Threads warten, um sich zu lösen . Dies ist, wie:
public class DateFormatTest {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public Date convert(String source) throws ParseException{
Date d = df.get().parse(source);
return d;
}
}
Hier ist eine gute Post mit mehr Details.
Andere Tipps
würde ich Daten Korruption erwarten - zum Beispiel wenn Sie zwei Termine zur gleichen Zeit sind Parsen, können Sie einen Anruf von Daten von einem anderen verunreinigt haben.
Es ist leicht, sich vorzustellen, wie das passieren konnte: oft Parsen beinhaltet eine bestimmte Menge an Zustand beibehalten wird, was Sie bisher gelesen habe. Wenn zwei Threads auf dem gleichen Zustand sowohl Trampeln, werden Sie Probleme bekommen. Zum Beispiel setzt DateFormat
ein calendar
Feld vom Typ Calendar
, und auf den Code von SimpleDateFormat
suchen, einige Methoden aufrufen calendar.set(...)
und andere Anruf calendar.get(...)
. Dies ist eindeutig nicht Thread-sicher.
Ich habe nicht sah in der genau Details, warum DateFormat
ist nicht Thread-sicher, aber für mich ist es genug, um zu wissen, dass es is unsicher ohne Synchronisation - die genaue Art und Weise von nicht-Sicherheit könnte auch zwischen den Versionen ändern.
Persönlich würde ich die Parser verwenden, um von Joda Zeit statt, wie sie ist fädelt sicher - und Joda Zeit ist ein viel besseres Datum und Zeit API beginnen mit:)
Wenn Sie mit Java 8, dann können Sie a href verwenden <= "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html" rel = "noreferrer „> DateTimeFormatter
.
Ein Formatierer aus einem Muster erstellt wird, kann beliebig oft verwendet werden, wie notwendig, es ist unveränderlich und ist Thread-sicher.
Code:
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String text = date.format(formatter);
System.out.println(text);
Ausgabe:
2017-04-17
Grob gesagt, dass Sie sollten nicht ein DateFormat
als Instanzvariable eines von vielen Themen, oder static
zugegriffen Objekts definieren.
Datumsformate werden nicht synchronisiert. Es wird empfohlen, eigene Format Instanzen für jeden Thread zu erstellen.
Also, falls Ihr Foo.handleBar(..)
von mehreren Threads zugegriffen wird, statt:
public class Foo {
private DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
public void handleBar(Bar bar) {
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
Sie verwenden sollten:
public class Foo {
public void handleBar(Bar bar) {
DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
Auch in allen Fällen haben keine static
DateFormat
Wie von Jon Skeet erwähnt, können Sie sowohl statische haben und eine gemeinsame Instanz-Variablen, falls Sie externe Synchronisation durchführen (das heißt synchronized
den Einsatz rund um Anrufe an die DateFormat
)
Datumsformate werden nicht synchronisiert. Es wird empfohlen, für jeden Thread getrennt Format Instanzen zu erstellen. Wenn mehrere Threads gleichzeitig ein Format zuzugreifen, müssen sie synchronisiert werden nach außen.
Dieses Mittel nehme an, Sie ein Objekt von Dateformat haben und Sie Zugriff auf denselben Gegenstand von zwei verschiedenen Threads und Sie rufen Format Methode auf das Objekt sowohl Gewinde auf dem gleichen Verfahren zur gleichen Zeit auf dem gleichen Objekt betreten werden, so dass Sie visualisieren es in der richtigen Ergebnis nicht führen
Wenn Sie mit Dateformat zu arbeiten haben jeder, wie dann sollten Sie etwas tun
public synchronized myFormat(){
// call here actual format method
}
sind Daten beschädigt. Gestern bemerkte ich es in meinem Multi-Thread-Programm, wo ich statisches DateFormat
Objekt hatte und nannte seine format()
für Werte über JDBC lesen. Ich hatte SQL-Anweisung SELECT, wo ich das gleiche Datum mit unterschiedlichen Namen lesen (SELECT date_from, date_from AS date_from1 ...
). Solche Aussagen wurden in 5 Verknüpfungen für verschiedene Termine in WHERE
clasue verwenden. Termine sahen „normal“, aber sie unterschieden sich in Wert - während alle Termine aus dem gleichen Jahr waren nur Monat und Tag gewechselt
Andere Antworten zeigen Ihnen den Weg, um solche Korruption zu vermeiden. Ich habe meinen DateFormat
nicht statisch, jetzt ist es ein Mitglied einer Klasse ist, die SQL-Anweisungen aufruft. Getestet habe ich auch statische Version mit synchronisiert werden. Beide arbeiteten gut mit keinem Unterschied in der Leistung.
Die Spezifikationen von Format, Number, Dateformat, Message usw. wurden nicht als Thread-sicher gestaltet. die Parse-Methode fordert Calendar.clone()
Methode auch, und es wirkt Kalender Fußabdrücke so viele Threads gleichzeitig Parsen wird das Klonieren der Kalender-Instanz ändern.
Für weitere, diese Fehler sind Berichte wie dieser und < a href = "http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=e7555c4e5577a514744f197010c9?bug_id=4264153" rel = "nofollow"> diese , mit den Ergebnissen der Date Thread-Sicherheit Ausgabe.
In der besten Antwort dogbane gab ein Beispiel von parse
Funktion und was es führt. Im Folgenden ist ein Code, lassen Sie sich format
Funktion überprüfen.
, dass Hinweis, wenn Sie die Anzahl der Testamentsvollstrecker (gleichzeitige Threads) ändern Sie unterschiedliche Ergebnisse erhalten. Aus meinen Experimenten:
- Leave
newFixedThreadPool
auf 5 und die Schleife wird jedes Mal scheitern. - Wird auf 1 gesetzt und die Schleife wird immer Arbeit (natürlich wie alle Aufgaben, die man tatsächlich ausgeführt werden durch ein)
- auf 2 und die Schleife hat nur etwa 6% Chance zu arbeiten.
Ich vermute, YMMV je nach Prozessor.
Die format
Funktion nicht durch die Zeit von einem anderen Thread zu formatieren. Dies liegt daran, intern format
Funktion calendar
Objekt verwendet, die bei der Inbetriebnahme der format
Funktion eingestellt ist. Und das calendar
Objekt ist eine Eigenschaft der SimpleDateFormat
Klasse. Seufz ...
/**
* Test SimpleDateFormat.format (non) thread-safety.
*
* @throws Exception
*/
private static void testFormatterSafety() throws Exception {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56);
final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56);
String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"};
Callable<String> task1 = new Callable<String>() {
@Override
public String call() throws Exception {
return "0#" + format.format(calendar1.getTime());
}
};
Callable<String> task2 = new Callable<String>() {
@Override
public String call() throws Exception {
return "1#" + format.format(calendar2.getTime());
}
};
//pool with X threads
// note that using more then CPU-threads will not give you a performance boost
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<String>> results = new ArrayList<>();
//perform some date conversions
for (int i = 0; i < 1000; i++) {
results.add(exec.submit(task1));
results.add(exec.submit(task2));
}
exec.shutdown();
//look at the results
for (Future<String> result : results) {
String answer = result.get();
String[] split = answer.split("#");
Integer calendarNo = Integer.parseInt(split[0]);
String formatted = split[1];
if (!expected[calendarNo].equals(formatted)) {
System.out.println("formatted: " + formatted);
System.out.println("expected: " + expected[calendarNo]);
System.out.println("answer: " + answer);
throw new Exception("formatted != expected");
/**
} else {
System.out.println("OK answer: " + answer);
/**/
}
}
System.out.println("OK: Loop finished");
}
Wenn mehrere Threads zu manipulieren / eine einzelne Instanz und Date Synchronisation nicht genutzt, den Zugriff, dann ist es möglich, verschlüsselten Ergebnisse zu erhalten. Das ist, weil mehrere nicht-atomaren Operationen Zustand zu ändern könnten oder Speicher zu sehen inkonsequent.
Das ist mein einfacher Code, zeigt Dateformat nicht sicher ist, fädelt.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateTimeChecker {
static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
runThread(target1);
runThread(target2);
runThread(target3);
}
public static void runThread(String target){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
Da alle Fäden die gleiche Simple Objekt verwenden, es wirft die folgende Ausnahme.
Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
Aber wenn wir verschiedene Objekte zu verschiedenen Threads passieren, der Code ausgeführt wird ohne Fehler.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateTimeChecker {
static DateFormat df;
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target1, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target2, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target3, df);
}
public static void runThread(String target, DateFormat df){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
Dies sind die Ergebnisse.
Thread-0 Thu Sep 28 17:29:30 IST 2000
Thread-2 Sat Sep 28 17:29:30 IST 2002
Thread-1 Fri Sep 28 17:29:30 IST 2001