问题的引出,这是一个多线程访问SimpleDateFormat的测试类
public class DateUtilTest {
public static class TestSimpleDateFormatThreadSafe extends Thread {
@Override
public void run() {
while ( true ) {
try {
this.join(2000);
} catch ( InterruptedException e1 ) {
e1.printStackTrace();
}
try {
System.out.println(this.getName() + ":" + DateUtil.parse("2013-05-24 06:02:20"));
} catch ( ParseException e ) {
e.printStackTrace();
}
}
}
}
public static void main( String[] args ) {
for ( int i = 0 ; i < 10 ; i++ ) {
new TestSimpleDateFormatThreadSafe().start();
}
}
}
运行时会出现如下Exception
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: multiple points
原因是DateFormate类中有个Calendar变量是全局,这个变量在子类SimpleDateFormat类在两个方法中被调用
private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate)
private void subFormat(int patternCharIndex, int count,FieldDelegate delegate, StringBuffer buffer,boolean useDateFormatSymbols)
这时多线程访问就是非线程安全的
public abstract class DateFormat extends Format {
protected Calendar calendar;
}
解决办法:
方法一:每次创建都new一个新的
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);
}
}
方法二:使用同步
public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
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);
}
}
}
方法三:使用ThreadLocal
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
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);
}
}
另一中ThreadLocal方法
public class ThreadLocalDateUtil {
private static final String date_format = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat()
{
DateFormat df = threadLocal.get();
if(df==null){
df = new SimpleDateFormat(date_format);
threadLocal.set(df);
}
return df;
}
public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
}
commons-lang.jar中提供了FastDateFormt,是一个thread-safe,性能上也能还是可以的
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getDefault(), Locale.getDefault());
DateFormatUtils.ISO_DATE_FORMAT.format(new Date())
DateFormatUtils是一个FastDateFormat工具类,提供了一些format常量
ISO_DATE_FORMAT yyyy-MM-dd"2004-01-02" ISO_DATE_TIME_ZONE_FORMAT yyyy-MM-ddZZ"2004-01-02-07:00" ISO_DATETIME_FORMAT yyyy-MM-dd'T'HH:mm:ss"2004-01-02T23:22:12" ISO_DATETIME_TIME_ZONE_FORMAT yyyy-MM-dd'T'HH:mm:ssZZ"2004-01-02T21:13:45-07:00" ISO_TIME_FORMAT 'T'HH:mm:ss"T04:23:22" ISO_TIME_NO_T_FORMAT HH:mm:ss"05:12:34" ISO_TIME_NO_T_TIME_ZONE_FORMAT HH:mm:ssZZ"12:32:22-07:00" ISO_TIME_TIME_ZONE_FORMAT 'T'HH:mm:ssZZ"T18:23:22-07:00" SMTP_DATETIME_FORMAT EEE, dd MMM yyyy HH:mm:ss Z"Wed, 01 Feb 2004 20:03:01 CST"