问题
Have a function that creates a time-only Date object. (why this is required is a long story which is irrelevant in this context but I need to compare to some stuff in XML world where TIME (i.e. time-only) is a valid concept).
private static final SimpleDateFormat DF_TIMEONLY = new SimpleDateFormat("HH:mm:ss.SSSZ");
public static Date getCurrentTimeOnly() {
String onlyTimeStr = DF_TIMEONLY.format(new Date()); // line #5
Date onlyTimeDt = null;
try {
onlyTimeDt = DF_TIMEONLY.parse(onlyTimeStr); // line #8
} catch (ParseException ex) {
// can never happen (you would think!)
}
return onlyTimeDt;
}
There are probably at least a couple other ways to create a time-only Date in Java (or more precisely one where the date part is 1970-01-01) but my question is really not about that.
My question is that this piece of code starts randomly throwing NumberFormatException on line #8 after having run in production for long time. Technically I would say that this should be impossible, right ?
Here's an extract of random NumberFormatExceptions that come from above piece of code:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ".11331133EE22"
java.lang.NumberFormatException: For input string: "880044E.3880044"
java.lang.NumberFormatException: For input string: "880044E.3880044E3"
First of all I hope we can agree that formally this should be impossible? The code uses the same format (DF_TIMEONLY) as output and then input. Let me know if you disagree that it should be impossible.
I haven't been able to re-produce the problem in a standalone environment. The problem seems to come when the JVM has run for a long time (>1 week). I cannot find a pattern to the problem, i.e. summer time / winter time, AM/PM, etc. The error is sporadic, meaning that one minute it will throw NumberFormatException and the next minute it will run fine.
I suspect that there's some kind of arithmetic malfunction somewhere in either the JVM or perhaps even in the CPU. The above exceptions suggests that there's floating point numbers involved but I fail to see where they would come from. As far as I know Java's Date object is a wrapper around a long which holds the number of millis since the epoch.
I'm guessing what is happening is that there's an unexpected string onlyTimeStr created in line #5 so the problem really lies here rather than in line #8.
Here's an example of a full stacktrace:
java.lang.NumberFormatException: For input string: "880044E.3880044E3"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1241)
at java.lang.Double.parseDouble(Double.java:540)
at java.text.DigitList.getDouble(DigitList.java:168)
at java.text.DecimalFormat.parse(DecimalFormat.java:1321)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2086)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
at java.text.DateFormat.parse(DateFormat.java:355)
at org.mannmann.zip.Tanker.getCurrentTimeOnly(Tanker.java:746)
Environment: Java 7
回答1:
The likely cause is the fact that SimpleDateFormat isn't threadsafe, and you're referencing it from multiple threads. While extremely difficult to prove (and about as hard to test for), there is some evidence this is the case:
.11331133EE22- notice how everything is doubled880044E.3880044E3- same here
You probably have at least two threads interleaving. The E was throwing me, I was thinking it was attempting to deal with scientific notation (1E10, etc), but it's likely part of the time zone.
Thankfully, the (formatting) basic fix is simple:
private static final String FORMAT_STRING = "HH:mm:ss.SSSZ";
public static Date getCurrentTimeOnly() {
SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_STRING);
String onlyTimeStr = formatter.format(new Date());
return formatter.parse(onlyTimeStr);
}
There's a couple of other things you could be doing here, too, with a few caveats:
1 - If the timezone is UTC (or any without DST), this is trivial
public static Date getCurrentTimeOnly() {
Date time = new Date();
time.setTime(time.getTime() % (24 * 60 * 60 * 1000));
return time;
}
2 - You're going to have trouble testing this method, because you can't safely pause the clock (you can change the timezone/locale). For a better time dealing with date/time in Java, use something like JodaTime. Note that LocalTime doesn't have a timezone attached, but Date only returns an offset in integer hours (and there are zones not on the hour); for safety, you need to either return a Calendar (with the full timezone), or just return something without it:
// This method is now more testable. Note this is only safe for non-DST zones
public static Calendar getCurrentTimeOnly() {
Calendar cal = new Calendar();
// DateTimeUtils is part of JodaTime, and is a class allowing you to pause time!
cal.setTimeInMillis(DateTimeUtils.currentTimeMillis() % (24 * 60 * 60 * 1000));
return cal;
}
回答2:
Joda-Time
FYI, the Joda-Time 2.3 library provides a class expressly for your purpose, time-only, without any date: LocalTime. And, it is thread-safe (immutable instances). Seems a much better option than manhandling the troublesome java.util.Date class.
LocalTime localTime = new LocalTime();
Dump to console…
System.out.println( "localTime: " + localTime );
When run…
localTime: 16:26:28.065
java.time
Java 8 brings the new java.time package, inspired by Joda-Time, defined by JSR 310.
In java.time, you will find a LocalTime class similar to the one in Joda-Time.
回答3:
I have got the same question, the cause is SimpleDateFormat is not thread-safe, I just add syncronized in the method, and it doesn't happen again.
回答4:
You can use "sychronized" block to make it thread safe. Something like:
synchronized (lastUpdatedFormat) {
date =
lastUpdatedFormat.parse(lastUpdatedFormat.format(currentDate));
}
回答5:
While the correct answer is the one by Clockwork-Muse (the cause of the problems is the fact that SimpleDateFormat isn't thread safe) I just wanted to deliver another method of creating a time-only Date object:
public static Date getCurrentTimeOnly() {
Calendar rightNow = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
int hour = rightNow.get(Calendar.HOUR_OF_DAY);
int minute = rightNow.get(Calendar.MINUTE);
int second = rightNow.get(Calendar.SECOND);
int msecond = rightNow.get(Calendar.MILLISECOND);
long millisSinceMidnight
= (hour * 60 * 60 * 1000)
+ (minute * 60 * 1000)
+ (second * 1000)
+ (msecond);
return new Date(millisSinceMidnight);
}
This method is somewhat more formally correct, i.e. it handles leap-seconds. It doesn't assume, like other methods, that all days since epoch has always had 24*60*60*1000 milliseconds in them.
It doesn't however handle the case where the leap second is on the current day.
回答6:
SimpleDateFormat is not thread safe. the following program will reproduce NumberFormatException while parsing string represented date to date object.
public class MaintainEqualThreadsPatallel {
static int parallelCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService executorPool = Executors.newFixedThreadPool(parallelCount);
int numberOfThreads = 150; // Total thread count = 150*2= 300.
List<Future<Object>> futureReturns = new LinkedList<Future<Object>>();
for (int i = 0; i < numberOfThreads; i++) {
int uniqueRandomValues = uniqueRandomValues(1, 10);
// Callable Thread - call()
Future<Object> submit = executorPool.submit( new WorkerCallable(uniqueRandomValues) );
futureReturns.add(submit);
// Runnable Thread - run()
executorPool.execute( new WorkerThread(uniqueRandomValues) );
}
// WorkerCallable: Blocking main thread until task completes.
waitTillThreadsCompleteWork(futureReturns);
// Terminate Pool threads in-order to terminate main thread
executorPool.shutdown();
}
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public static Date numberFormatEx(Date date) throws ParseException { // synchronized
String dateStr = sdf.format(date);
Date dateParsed = sdf.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
protected void loopFunction(int repeatCount) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName +":START");
for (int i = 1; i <= repeatCount; i++) {
try {
System.out.println(threadName +":"+ i);
sleepThread(100);
numberFormatEx(new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(threadName +":END");
}
public static void waitTillThreadsCompleteWork(List<Future<Object>> futureReturns) throws Exception {
for (Future<Object> future : futureReturns) {
int threadReturnVal = (int) future.get();
System.out.println("Future Response : "+threadReturnVal);
}
}
public static int uniqueRandomValues(int min, int max) {
int nextInt = ThreadLocalRandom.current().nextInt(min, max);
System.out.println("Random Vlaue : "+nextInt);
return nextInt;
}
public void sleepThread(long mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class WorkerThread extends MaintainEqualThreadsPatallel implements Runnable {
int randomValue = 0;
public WorkerThread(int randomValue) {
this.randomValue = randomValue;
}
@Override
public void run() {
// As separate stack run() function doesn't accepts parameters, pass to constructor.
loopFunction(randomValue);
}
}
class WorkerCallable extends MaintainEqualThreadsPatallel implements Callable<Object> {
int randomValue = 0;
public WorkerCallable(int randomValue) {
this.randomValue = randomValue;
}
public Object call() {
// As separate stack run() function doesn't accepts parameters, pass to constructor.
loopFunction(randomValue);
return randomValue;
}
}
NumberFormatException with different messages:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: "186E.2"
java.lang.NumberFormatException: For input string: "186E.2186E2"
java.lang.NumberFormatException: For input string: "22200222E.222002224EE4"
java.lang.NumberFormatException: For input string: "22200222E.222002224EE44"
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)
In Multi-Threading/Web Application with Multi-Requests concept parse function leads to NumberFormatException which can be handled using synchronized block.
To overcome NumberFormatException on parse() function use any of the following scenarios.
- Separate Object: Every request/thread works on its own object.
public static Date numberFormatEx(Date date) throws ParseException {
SimpleDateFormat ObjInstance = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
String dateStr = ObjInstance.format(date);
Date dateParsed = ObjInstance.parse(dateStr);
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
Unnecessary creating reusable object for each thread.
- Static Object synchronized block: Every request/thread shares the common object to perform operation. As multiple threads share same object at same time then the object data gets clear/overrride
""/"186E.2186E2"at some point and leads to error.
static SimpleDateFormat objStatic = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public static synchronized Date numberFormatEx(Date date) throws ParseException {
String dateStr = objStatic.format(date);
Date dateParsed = objStatic.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
NOTE: In case of Memory management it better to use synchronized block with static object which is reusable.
回答7:
The diagnosis in the accepted answer is correct. I am providing the modern answer: do use java.time, the modern Java date and time API, for your date and time work. In Java 7 too. SimpleDateFormat is notoriously troublesome, its lack of thread safety is only one of its many problems. So don’t use that class.
OffsetTime.now() and ThreeTen Backport
You want the current time only, though with an offset from UTC, if your format pattern is to be believed. We have got a method exactly for that in java.time, the modern Java date and time API. So no reason to format into a string and parse back.
OffsetTime timeOnly = OffsetTime.now(ZoneId.systemDefault());
System.out.println(timeOnly);
When I ran the code just now in my time zone, Europe/Copenhagen, on jdk.1.7.0_67, the output was:
06:21:55.419+01:00
By the way this is also the XML format for the concept of a time with time zone. Are we done?
The Date class you were returning is poorly designed and long outdated, so avoid it if you can. If you need one for a legacy API that you cannot afford to change just now, convert like this:
Instant asInstant = LocalDate.of(1970, Month.JANUARY, 1)
.atTime(timeOnly)
.toInstant();
Date oldfashionedDateObject = DateTimeUtils.toDate(asInstant);
System.out.println("As java.util.Date: " + oldfashionedDateObject);
As java.util.Date: Thu Jan 01 06:21:55 CET 1970
Question: Does it work on Java 7?
Environment: Java 7
java.time just requires at least Java 6.
- In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. In this case use
Date.from(asInstant)for converting fromInstanttoDateinstead of the way shown in the code above. - In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
- On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from
org.threeten.bpwith subpackages.
Links
- Oracle tutorial: Date Time explaining how to use java.time.
- Java Specification Request (JSR) 310, where
java.timewas first described. - ThreeTen Backport project, the backport of
java.timeto Java 6 and 7 (ThreeTen for JSR-310). - ThreeTenABP, Android edition of ThreeTen Backport
- Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
- Wikipedia article: ISO 8601
来源:https://stackoverflow.com/questions/21017502/numberformatexception-while-parsing-date-with-simpledateformat-parse