根据javadocs描述,DateFormat类是非线程安全的。通过多线程并发测试,也证实了这一点。
通过此次测试得出的一些经验:
- 经多线程并发UT验证,DateFormat的format一直都正确运行(基于StringBuffer实现),但parse经常出问题(未使用任何并发技术)
- 即使DateFormat的parse运行正常结束,最终结果也可能不对!
- DateFormatThreadLocal、DateTimeFormatterWrapper与FastDateFormatWrapper都正确运行
- 通过500并发量+5个线程的性能测试来看,对于format操作,DateTimeFormatterWrapper 最优,FastDateFormatWrapper 次之,DateFormatThreadLocal 最差;对于parse操作,三者差距微乎其微
/**
* 可格式化的日期接口定义。
*
* @author Bert Lee
* @version 2014-8-16
*/
public interface DateFormattable {
/**
* 默认日期格式
*/
String PATTERN = "yyyy-MM-dd";
/**
* Formats a Date into a date/time string.
*
* @param date
* @return
* @see java.text.DateFormat#format(Date)
*/
String format(Date date);
/**
* Parses text from the beginning of the given string to produce a date.
*
* @param source
* @return
* @throws ParseException
* @see java.text.DateFormat#parse(String)
*/
Date parse(String source) throws ParseException;
}
/**
* {@link DateFormat} wrapper.
*
* @author Bert Lee
* @version 2014-8-16
*/
public class DateFormatWrapper implements DateFormattable {
private final DateFormat format;
public DateFormatWrapper() {
this(PATTERN);
}
public DateFormatWrapper(String pattern) {
this.format = new SimpleDateFormat(pattern);
}
/**
* <font color="red">经多线程UT验证,format都正确运行(基于StringBuffer实现)!</font>
*/
@Override
public String format(Date date) {
return this.format.format(date);
}
/**
* <font color="red">经多线程UT验证,parse经常出问题(未使用任何并发技术)!</font>
*/
@Override
public Date parse(String source) throws ParseException {
return this.format.parse(source);
}
}
/**
* Date parse task.
*
* @author Bert Lee
* @version 2014-8-16
*/
public class DateParseCallable implements Callable<Date> {
private DateFormattable formatter;
private String source;
public DateParseCallable(DateFormattable formatter, String source) {
this.formatter = formatter;
this.source = source;
}
@Override
public Date call() throws Exception {
return this.formatter.parse(source);
}
}
/**
* Concurrent thread {@link java.util.concurrent.Executor Executor} wrapper.
*
* @author Bert Lee
* @version 2014-8-16
*/
public class ConcurrentThreadExecutorWrapper<T> {
private ExecutorService executor;
private Callable<T> task;
private List<Future<T>> results;
private TestScale runTimes;
public ConcurrentThreadExecutorWrapper(Callable<T> task) {
this(task, TestScale.BASIC);
}
public ConcurrentThreadExecutorWrapper(Callable<T> task, TestScale runTimes) {
// pool with 5 threads
this.executor = Executors.newFixedThreadPool(5);
this.task = task;
this.runTimes = runTimes;
}
public void run() {
results = new ArrayList<Future<T>>();
// perform 10 times date conversions
for (int i = 0; i < runTimes.getValue(); i++) {
results.add(executor.submit(task));
}
executor.shutdown();
}
public void printResults() throws Exception {
this.run();
// look at the results
for (Future<T> result : results) {
out.println(result.get());
}
}
}
/**
* Test for {@link ConcurrentThreadExecutorWrapper}.
* <p>
* 参考并优化实现了<a href="http://stackoverflow.com/questions/4021151/java-dateformat-is-not-threadsafe-what-does-this-leads-to">
* “Java DateFormat is not thread-safe” what does this leads to?</a>
*
* @author Bert Lee
* @version 2014-8-16
*/
public class DateFormatExecutorTest {
// 日期转换格式
private static final String pattern = "yyyy-MM-dd HH:mm";
/*
* 经多线程UT验证,DateFormat的format一直都正确运行(基于StringBuffer实现),但parse经常出问题(未使用任何并发技术)!
* 即使DateFormat的parse运行正常结束,最终结果也可能不对!
*
* DateFormatThreadLocal、DateTimeFormatterWrapper与FastDateFormatWrapper都正确运行。
*/
@Test(dataProvider = "parse", groups = "parse")
public void parse(DateFormattable formatter, String source) throws Exception {
Callable<Date> task = new DateParseCallable(formatter, source);
ConcurrentThreadExecutorWrapper<Date> executor = new ConcurrentThreadExecutorWrapper<Date>(task);
out.println(formatter.getClass().getSimpleName() + " parse result:");
executor.printResults();
}
@DataProvider(name = "parse")
protected static final Object[][] parseTestData() {
Object[][] testData = new Object[][] {
// { new DateFormatWrapper(pattern), "2014-08-16 08:23:07" }, // 经常报错,即使运行结束,最终结果也可能不对!
{ new DateFormatThreadLocal(pattern), "2014-08-16 08:23:07" },
{ new DateTimeFormatterWrapper(pattern), "2014-08-16 08:23" },
{ new FastDateFormatWrapper(pattern), "2014-08-16 08:23:07" },
};
return testData;
}
@Test(dataProvider = "format", groups = "format")
public void format(DateFormattable formatter) throws Exception {
Date date = new Date();
Callable<String> task = new DateFormatCallable(formatter, date);
ConcurrentThreadExecutorWrapper<String> executor = new ConcurrentThreadExecutorWrapper<String>(task);
out.println(formatter.getClass().getSimpleName() + " format result:");
executor.printResults();
}
@DataProvider(name = "format")
protected static final Object[][] formatTestData() {
Object[][] testData = new Object[][] {
{ new DateFormatWrapper(pattern) },
{ new DateFormatThreadLocal(pattern) },
{ new DateTimeFormatterWrapper(pattern) },
{ new FastDateFormatWrapper(pattern) },
};
return testData;
}
}
为了解决并发问题,参考了StackOverflow上的这篇文章《“Java DateFormat is not thread-safe” what does this leads to?》和《Java Best Practices – DateFormat in a Multithreading Environment》,在此基础上进行了重构及性能测试。文章提供了三种解决方案:
- 基于
ThreadLocal实现:使用一个
ThreadLocal
变量来持有DateFormat
对象 - 直接使用Joda-Time的DateTimeFormatter
- 直接使用Apache Commons Lang 3.x的FastDateFormat
通过500并发量+5个线程的性能测试来看,对于format操作,DateTimeFormatterWrapper 最优,FastDateFormatWrapper 次之,DateFormatThreadLocal 最差;对于parse操作,三者差距微乎其微。
/**
* {@link DateFormat} thread-local.
*
* @author Bert Lee
* @version 2014-8-16
*/
public class DateFormatThreadLocal implements DateFormattable {
private final ThreadLocal<DateFormat> format;
public DateFormatThreadLocal() {
this(PATTERN);
}
public DateFormatThreadLocal(final String pattern) {
this.format = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
}
@Override
public String format(Date date) {
return this.format.get().format(date);
}
@Override
public Date parse(String source) throws ParseException {
return this.format.get().parse(source);
}
}
/**
* {@link DateTimeFormatter} wrapper.
*
* @author Bert Lee
* @version 2014-8-19
*/
public class DateTimeFormatterWrapper implements DateFormattable {
private final DateTimeFormatter format;
public DateTimeFormatterWrapper() {
this(PATTERN);
}
public DateTimeFormatterWrapper(String pattern) {
this.format = DateTimeFormat.forPattern(pattern);
}
@Override
public String format(Date date) {
return this.format.print(date.getTime());
}
/**
* <font color="red">日期字符串表示要与日期模式完全匹配,不然会抛异常!</font>
*/
@Override
public Date parse(String source) throws ParseException {
DateTime dt = this.format.parseDateTime(source);
return dt.toDate();
}
}
/**
* {@link FastDateFormat} is a fast and thread-safe version of
* {@link java.text.SimpleDateFormat}.<p>
*
* FastDateFormat implements the behavior of Java 7.
*
* @author Bert Lee
* @version 2014-8-26
*/
public class FastDateFormatWrapper implements DateFormattable {
private final FastDateFormat format;
public FastDateFormatWrapper() {
this(PATTERN);
}
public FastDateFormatWrapper(String pattern) {
this.format = FastDateFormat.getInstance(pattern);
}
@Override
public String format(Date date) {
return this.format.format(date);
}
@Override
public Date parse(String source) throws ParseException {
return this.format.parse(source);
}
}
public class DateFormatExecutorTest {
// 日期转换格式
private static final String pattern = "yyyy-MM-dd HH:mm";
/*
* 通过500并发量+5个线程的性能测试来看,
* 对于format操作,DateTimeFormatterWrapper 最优,FastDateFormatWrapper 次之,DateFormatThreadLocal 最差;
* 对于parse操作,三者差距微乎其微。
*/
@Test(dataProvider = "profileParse", groups = "profile")
public void profileParse(DateFormattable formatter, String source) throws Exception {
String className = formatter.getClass().getSimpleName() + "'s parse";
RunTimeStats timeStats = new RunTimeStats(className);
Callable<Date> task = new DateParseCallable(formatter, source);
ConcurrentThreadExecutorWrapper<Date> executor = new ConcurrentThreadExecutorWrapper<Date>(task, TestScale.SMALL);
executor.run();
timeStats.print();
}
@DataProvider(name = "profileParse")
protected static final Object[][] profileParseTestData() {
Object[][] testData = new Object[][] {
{ new DateFormatThreadLocal(pattern), "2014-08-16 08:23:07"},
{ new DateTimeFormatterWrapper(pattern), "2014-08-16 08:23" },
{ new FastDateFormatWrapper(pattern), "2014-08-16 08:23:07" },
};
return testData;
}
@Test(dataProvider = "profileFormat", groups = "profile")
public void profileFormat(DateFormattable formatter) throws Exception {
String className = formatter.getClass().getSimpleName() + "'s format";
RunTimeStats timeStats = new RunTimeStats(className);
Date date = new Date();
Callable<String> task = new DateFormatCallable(formatter, date);
ConcurrentThreadExecutorWrapper<String> executor = new ConcurrentThreadExecutorWrapper<String>(task, TestScale.MIDDLE);
executor.run();
timeStats.print();
}
@DataProvider(name = "profileFormat")
protected static final Object[][] profileFormatTestData() {
Object[][] testData = new Object[][] {
{ new DateFormatThreadLocal(pattern) },
{ new DateTimeFormatterWrapper(pattern) },
{ new FastDateFormatWrapper(pattern) },
};
return testData;
}
}
完整的源码见附件啦~
玩得开心!^_^