As we all know SimpleDateFormat class is used to format and parse dates in Java with patterns like yyyy-MM-dd HH:mm:ss
, and then use that instance to format and parse dates to or from string.
SimpleDateFormat
class is that it is not thread-safe and causes issues in multi-threaded environments if not used properly.
Example of Thread Safety problem in SimpleDateFormat
Letβs understand what happens when we try to use SimpleDateFormat
in a multi-threaded environment without any synchronization.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2015-07-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
We have a single instance of SimpleDateFormat
that we use to parse dates from multiple threads.
it will produce an output like this –
# Output
Successfully Parsed Date Fri Jul 22 10:00:00 IST 2015
Successfully Parsed Date Thu Jul 22 10:00:00 IST 2015
java.lang.NumberFormatException: multiple points
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:543)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2098)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1915)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1529)
at java.base/java.text.DateFormat.parse(DateFormat.java:386)
at SimpleDateFormatThreadExample.parseDate(SimpleDateFormatThreadExample.java:32)
at SimpleDateFormatThreadExample.access$000(SimpleDateFormatThreadExample.java:8)
at SimpleDateFormatThreadExample$1.run(SimpleDateFormatThreadExample.java:19)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.base/java.lang.Thread.run(Thread.java:844)
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:702)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2093)
This class mutates its internal state for formatting and parsing dates. Thatβs why it results in these issues when multiple threads use the same instance of SimpleDateFormat
concurrently.
How to use SimpleDateFormat in a multi-threaded environment?
- Create a new instance of
SimpleDateFormat
for each thread. - Synchronize concurrent access by multiple threads with a
synchronized
keyword or alock
.
1. Create separate instances of SimpleDateFormat
for every thread
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadExample {
public static void main(String[] args) {
String dateStr = "2015-07-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
The output of the above program wonβt have any errors –
# Output
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
2.Share the same instance of SimpleDateFormat
but synchronize concurrent access
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2015-07-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private synchronized static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java 8 has a better and more enhanced DateTimeFormatter which is also thread-safe.