Recently I was asked by one of our team mate that during load testing, order creation was failing (One of the application I am working on is an Order Management Application) because of wrong inputs. However, the unit tests, with the same input was working fine. Investigation into it, led to (once again) an old jdk "designed" bug 4146524 (or feature). After seeing such issues in a number of applications, I am sure a number of developers are yet not aware of Format objects are not thread-safe. So if you create a Format object (or a MessageFormat, NumberFormat, DecimalFormat, ChoiceFormat, DateFormat or SimpleDateFormat object), it cannot be shared among threads.
A number of write ups are available on internet to make sure your Format objects can work on multi threaded environment. Recreating it below, if it can help some :
Thread unsafe implementation
package aminur.test.formatters;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
public final class ThreadUnsafePriceFormatter {
// the price formats
private static final DecimalFormat pointSeparatedFormat;
static {
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
pointSeparatedFormat = new DecimalFormat("#0.00", symbols);
}
private ThreadUnsafePriceFormatter() {
}
public static String formatPointSeparated(double price) {
return pointSeparatedFormat.format(price);
}
public static Number parsePointSeparated(String price)
throws ParseException {
return pointSeparatedFormat.parse(price);
}
}
Problem, In multi-threaded environment, it can even throw exception.
private static void testThreadUnSafeFormatter() throws Exception {
Callable<Number> task = new Callable<Number>() {
public Number call() throws Exception {
return ThreadUnsafePriceFormatter.parsePointSeparated("2.2");
}
};
executeTasks(task);
}
private static void executeTasks(final Callable<Number> task)
throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(100);
List<Future<Number>> results = new ArrayList<Future<Number>>();
for (int i = 0; i < 150; i++) {
results.add(exec.submit(task));
}
exec.shutdown();
for (Future<Number> result : results) {
System.out.println(result.get());
}
}
Thread safe implementation
I used the most recommended method ThreadLocal to provide a thread based copy of Format object.
package aminur.test.formatters;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
public class ThreadSafePriceFormatter {
// the price formats
private static final ThreadLocal<DecimalFormat> pointSeparatedFormat = new ThreadLocal<DecimalFormat>() {
@Override
protected DecimalFormat initialValue() {
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
return (new DecimalFormat("#0.00", symbols));
}
};
private ThreadSafePriceFormatter() {
}
public static String formatPointSeparated(double price) {
return pointSeparatedFormat.get().format(price);
}
public static Number parsePointSeparated(String price)
throws ParseException {
return pointSeparatedFormat.get().parse(price);
}
}