想必大家对SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。
常规代码如下:
1import java.text.ParseException;
2import java.text.SimpleDateFormat;
3import java.util.Date;
4public class DateUtil {
5
6 public static String formatDate(Date date)throws ParseException{
7 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
8 return sdf.format(date);
9 }
10
11 public static Date parse(String strDate) throws ParseException{
12 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
13 return sdf.parse(strDate);
14 }
15}
你也许会说,OK,那我就创建一个静态的simpleDateFormat实例,然后放到一个DateUtil类(如下)中,在使用时直接使用这个实例进行操作,这样问题就解决了。改进后的代码如下:
1import java.text.ParseException;
2import java.text.SimpleDateFormat;
3import java.util.Date;
4
5public class DateUtil {
6 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
7
8 public static String formatDate(Date date)throws ParseException{
9 return sdf.format(date);
10 }
11
12 public static Date parse(String strDate) throws ParseException{
13
14 return sdf.parse(strDate);
15 }
16}
当然,这个方法的确很不错,在大部分的时间里面都会工作得很好。但当你在生产环境中使用一段时间之后,你就会发现这么一个事实:它不是线程安全的。在正常的测试情况之下,都没有问题,但一旦在生产环境中一定负载情况下时,这个问题就出来了。他会出现各种不同的情况,比如转化的时间不正确,比如报错,比如线程被挂死等等。我们看下面的测试用例,那事实说话:
1import java.text.ParseException;
2import java.util.Date;
3
4public class DateUtilTest {
5
6 public static class TestSimpleDateFormatThreadSafe extends Thread {
7 @Override
8 public void run() {
9 while(true) {
10 try {
11 this.join(2000);
12 } catch (InterruptedException e1) {
13 e1.printStackTrace();
14 }
15 try {
16 System.out.println(this.getName()+":"+DateUtil.parse("2013-05-24 06:02:20"));
17 } catch (ParseException e) {
18 e.printStackTrace();
19 }
20 }
21 }
22 }
23
24
25 public static void main(String[] args) {
26 for(int i = 0; i < 3; i++){
27 new TestSimpleDateFormatThreadSafe().start();
28 }
29
30 }
31}
运行后项目报错:
1Exception in thread "Thread-1" Exception in thread "Thread-0" Exception in thread "Thread-2" java.lang.NumberFormatException: multiple points
2 at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
3 at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
4 at java.lang.Double.parseDouble(Double.java:538)
5 at java.text.DigitList.getDouble(DigitList.java:169)
6 at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
7 at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
8 at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
9 at java.text.DateFormat.parse(DateFormat.java:364)
10 at DateUtil.parse(DateUtil.java:14)
11 at DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:15)
12java.lang.NumberFormatException: multiple points
13 at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
14 at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
15 at java.lang.Double.parseDouble(Double.java:538)
16 at java.text.DigitList.getDouble(DigitList.java:169)
17 at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
18 at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
19 at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
20 at java.text.DateFormat.parse(DateFormat.java:364)
21 at DateUtil.parse(DateUtil.java:14)
22 at DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:15)
23java.lang.NumberFormatException: multiple points
24 at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
25 at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
26 at java.lang.Double.parseDouble(Double.java:538)
27 at java.text.DigitList.getDouble(DigitList.java:169)
28 at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
29 at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
30 at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
31 at java.text.DateFormat.parse(DateFormat.java:364)
32 at DateUtil.parse(DateUtil.java:14)
33 at DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:15)
方案:使用ThreadLocal:
1import java.text.DateFormat;
2import java.text.ParseException;
3import java.text.SimpleDateFormat;
4import java.util.Date;
5
6public class ConcurrentDateUtil {
7
8 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
9 @Override
10 protected DateFormat initialValue() {
11 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
12 }
13 };
14
15 public static Date parse(String dateStr) throws ParseException {
16 return threadLocal.get().parse(dateStr);
17 }
18
19 public static String format(Date date) {
20 return threadLocal.get().format(date);
21 }
22}
23
24或
25
26import java.text.DateFormat;
27import java.text.ParseException;
28import java.text.SimpleDateFormat;
29import java.util.Date;
30
31public class ThreadLocalDateUtil {
32 private static final String date_format = "yyyy-MM-dd HH:mm:ss";
33 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
34
35 public static DateFormat getDateFormat()
36 {
37 DateFormat df = threadLocal.get();
38 if(df==null){
39 df = new SimpleDateFormat(date_format);
40 threadLocal.set(df);
41 }
42 return df;
43 }
44
45 public static String formatDate(Date date) throws ParseException {
46 return getDateFormat().format(date);
47 }
48
49 public static Date parse(String strDate) throws ParseException {
50 return getDateFormat().parse(strDate);
51 }
52}
说明:使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。
其他方案:抛弃JDK中的SimpleDateFormat,使用其他类库中的时间格式化类:
1.使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat
2.使用Joda-Time类库来处理时间相关问题
Joda-Time引用方式:
1<dependency>
2 <groupId>joda-time</groupId>
3 <artifactId>joda-time</artifactId>
4 <version>2.10</version>
5</dependency>
DateUtils工具类(微信文章字数有限,给出链接作为参考):
http://daimajia.iteye.com/blog/2305278