It’s a little alarming how many good developers are unaware that many standard Java classes – including subclasses of Format – are not thread safe. Many are also not sure about how their applications perform in a multi-threaded environment or how their web application container (Tomcat) will run their app in multiple threads. This can cause nasty intermittent bugs that can be incredibly hard to find and fix. It’s important to be aware of threading issues at development time but it’s also important to be able to test for them.
SimpleDateFormat anti-pattern
DateFormat, SimpleDateFormat, MessageFormat and NumberFormat classes are described in their Javadocs as not synchronized. The warning given on each is:
It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.This means that declaring an instance of SimpleDateFormat as a constant (static final class member) is usually bad. But then again, it’s a very obvious thing to do.
I have a Spring MVC Controller that I want to parse a date String. The date String is always in the format dd/MM/yyyy. So I just need one SimpleDateFormat constant to parse all my dates. Like this:
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy"); @RequestMapping(value = "/staticDateFormatExample", method = RequestMethod.POST) public ModelAndView submitForm(@ModelAttribute(MODEL_DATE_FORM) DateForm dateForm) throws ParseException { String dateString = dateForm.getDateString(); Date parsedDate = DATE_FORMAT.parse(dateString); return new ModelAndView(VIEW_DISPLAY, MODEL_PARSED_DATE, parsedDate); }
I’ve written a unit test for the above and it works. I’ve tried running the application in a browser and it works. So what’s the problem?
Executing in Threads
The problem occurs if more than one thread tries to executeDATE_FORMAT.parse(dateString); at the same time. As the parse method is not synchronized it can get halfway through parsing one String when it switches to another thread and starts parsing another. It’s not designed to handle that and will return undefined results.
Even if you’ve avoided creating a new thread anywhere in your application code, your web application will most likely run concurrently in multiple threads. This is because it’s the container (eg. Tomcat) that’s creating threads for you. When Tomcat receives multiple requests from different users it may service those requests simultaneously in different threads. In the example above, we could have multiple threads all dispatching requests to our Controller. So Spring MVC Controllers and indeed all web application code must be thread safe and must use objects in a thread safe manner.
Testing for failure
A load testing tool such as WAPT or AppPerfect can be used to find synchronization problems. There are many commercial applications that can generate load to an application but SmartBear’s LoadUI is a good free one. It can be set up to record user sessions and then repeatedly replay them to simulate load.
To demonstrate the problem with my static SimpleDateFormat, I recorded two scenarios in LoadUI. One where the user enters ’01/01/2001′ and one where the user enters ’31/12/2013′. I then created a test that executes both scenarios simultaneously. This simulates two different users both using the application at the same time. To increase the likelihood of a synchronization error, I increased the number of user sessions to 50 for both groups. So this now simulates 100 different users all using the application at the same time.
Finally, I set this to run a continuous load for 15 minutes. So I now have 100 virtual users all repeatedly entering date strings into my form.
Verifying results
LoadUI will run these tests and report all HTTP 200 responses as success and HTTP error codes (500, 404 etc) as failures. However, I don’t expect my app to throw an error. I just expect the date to be parsed incorrectly. Additional page verification rules can be added to check that the page response is as expected.
When this test is left to run for 15 minutes, it completes around 14000 requests, almost all of which are successful. However, a small number fail:
Looking at the detail of the failures, we can see that the dates shown on the page do not match the expected dates.
Fixing the problem
The issue here is that a single (static) instance of SimpleDateFormat is shared across multiple threads. The simplest solution is to create a SimpleDateFormat object as a variable local to the controller method rather than an instance variable of the Controller class. Note that simply changing the static member to a regular instance variable is not sufficient as a single Controller instance may still be accessed by multiple threads.
@RequestMapping(value = "/staticDateFormatExample", method = RequestMethod.POST) public ModelAndView submitForm(@ModelAttribute(MODEL_DATE_FORM) DateForm dateForm) throws ParseException { SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); String dateString = dateForm.getDateString(); Date parsedDate = dateFormat.parse(dateString); return new ModelAndView(VIEW_DISPLAY, MODEL_PARSED_DATE, parsedDate); }
There’s a very slight performance overhead to doing this – a new instance is created for every request rather than once per application. However, this is the simplest way to avoid concurrency issues with this class.
you could also wrap the SimpleDateFormat object in as a ThreadLocal