`

Java DateFormat并发实现

    博客分类:
  • Java
阅读更多

    根据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》,在此基础上进行了重构及性能测试。文章提供了三种解决方案:

  1. 基于ThreadLocal实现:使用一个ThreadLocal变量来持有DateFormat对象
  2. 直接使用Joda-Time的DateTimeFormatter
  3. 直接使用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;
	}

}
 

    完整的源码见附件啦~

 

 

玩得开心!^_^

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics