class="Apple-converted-space">??? 根据javadocs描述,DateFormat类是非线程安全的。通过多线程测试,也证实了这一点。
/** * 可格式化的日期接口定义。 * * @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都正确运行!</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一直都正确运行,但parse经常出问题!
* 即使DateFormat的parse运行正常结束,最终结果也可能不对!
*
* DateFormatThreadLocal与DateTimeFormatterWrapper都正确运行。
*/
@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" },
};
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) },
};
return testData;
}
}
?
???? 为了解决并发问题,参考了StackOverflow上的这篇文章《“Java DateFormat is not thread-safe” what does this leads to?》,在此基础上进行了重构及性能测试。文章提供了两种解决方案:
ThreadLocal实现:使用一个ThreadLocal变量来持有DateFormat对象经多线程性能测试,DateTimeFormatter要优于DateFormatThreadLocal。
/**
* {@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();
}
}
?
public class DateFormatExecutorTest {
// 日期转换格式
private static final String pattern = "yyyy-MM-dd HH:mm";
/*
* 经多线程性能测试,DateTimeFormatter要优于DateFormatThreadLocal。
*/
@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.TINY);
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" },
};
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.TINY);
executor.run();
timeStats.print();
}
@DataProvider(name = "profileFormat")
protected static final Object[][] profileFormatTestData() {
Object[][] testData = new Object[][] {
{ new DateFormatThreadLocal(pattern) },
{ new DateTimeFormatterWrapper(pattern) },
};
return testData;
}
}
???? 完整的源码见附件啦~
?
玩得开心!