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; } }?
??? 完整的源码见附件啦~
?
玩得开心!