16boke - 一路博客

多线程环境下SimpleDateFormat的异常问题

SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。

看一下SimpleDateFormat类的定义:

 * Date formats are not synchronized.
 * It is recommended to create separate format instances for each thread.
 * If multiple threads access a format concurrently, it must be synchronized
 * externally.
 *
 * @see          <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
 * @see          java.util.Calendar
 * @see          java.util.TimeZone
 * @see          DateFormat
 * @see          DateFormatSymbols
 * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
 */
public class SimpleDateFormat extends DateFormat {

javadoc明确说明由于DateFormat不是线程安全的,所以强烈要求在每个线程中进行实例化后再使用。

如果在多线程环境下安全的使用SimpleDateFormat呢,最简单的方法就是在具体的方法中实例化SimpleDateFormat对象,但是这种方法确是最消耗资源的,我们也提供一下这种方法:

public class DateUtil {
    
    public static  String formatDate(Date date)throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }
}

1、使用synchronized

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 第一种线程安全的方法:使用synchronized传入sdf不变对象
public static String formatDate(Date date) throws ParseException {
	synchronized (sdf) {
		return sdf.format(date);
	}
}

public static Date parse(String strDate) throws ParseException {
	synchronized (sdf) {
		return sdf.parse(strDate);
	}
}

2、使用ThreadLocal

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
	@Override
	protected DateFormat initialValue() {
		System.out.println("new...");
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	}
};

public static Date parse(String dateStr) throws ParseException {
	return threadLocal.get().parse(dateStr);
}

public static String format(Date date) {
	return threadLocal.get().format(date);
}

3、再提供一种使用ThreadLocal

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
private static DateFormat getDateFormat(){
	DateFormat df = threadLocal.get();
	System.out.println("df = "+df);
	if(df==null){
		df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		threadLocal.set(df);
	}
	return df;
}

public static Date parse(String dateStr) throws ParseException {
	return getDateFormat().parse(dateStr);
}

public static String format(Date date) {
	return getDateFormat().format(date);
}

4、多线程的测试类

package com.dateformat;

import java.text.ParseException;

public class DateUtilTest {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true) {
						try {
							//当前线程中断2秒
							Thread.currentThread().join(2000);
						} catch (InterruptedException e1) {
							e1.printStackTrace();
						}
						try {
							System.out.println(Thread.currentThread().getName() + ":" + DateUtil.parse("2016-08-01 06:02:20"));
						} catch (ParseException e) {
							e.printStackTrace();
						}
					}
				}
			}).start();
		}
	}
}

Java