-解决SimpleDateFormat的线程不安全问题的方法

转载自:http://bijian1013.iteye.com/blog/1873336

在Java项目中,我们通常会自己写一个DateUtil类,处理日期和字符串的转换,如下所示:



Java代码 
1.public class DateUtil01 { 
2. 
3.    private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
4. 
5.    public void format(Date date) { 
6.        System.out.println(dateformat.format(date)); 
7.    } 
8. 
9.    public void parse(String str) { 
10.        try { 
11.            System.out.println(dateformat.parse(str)); 
12.        } catch (ParseException e) { 
13.            e.printStackTrace(); 
14.        } 
15.    } 
16.} 

然而,由于SimpleDateFormat类不是线程安全的,所以在多线程的环境下,往往会出现意想不到的结果。
如下是我写的测试其是否存在安全问题的实例:

1.日期工具处理类的接口



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.util.Date; 
4. 
5.public interface DateUtilInterface { 
6. 
7.    public void format(Date date); 
8.    public void parse(String str); 
9.} 



2.日期工具实现类



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.text.ParseException; 
4.import java.text.SimpleDateFormat; 
5.import java.util.Date; 
6. 
7.public class DateUtil01 implements DateUtilInterface { 
8. 
9.    private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
10. 
11.    @Override 
12.    public void format(Date date) { 
13.        System.out.println(dateformat.format(date)); 
14.    } 
15. 
16.    @Override 
17.    public void parse(String str) { 
18.        try { 
19.            System.out.println(dateformat.parse(str)); 
20.        } catch (ParseException e) { 
21.            e.printStackTrace(); 
22.        } 
23.    } 
24.} 



3.调用日期工具的线程类



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.util.Calendar; 
4.import java.util.Date; 
5. 
6.public class DateThread implements Runnable { 
7. 
8.    DateUtilInterface dateUtil = null; 
9. 
10.    public DateThread(DateUtilInterface dateUtil) { 
11.        this.dateUtil = dateUtil; 
12.    } 
13. 
14.    public void run() { 
15.        int year = 2000; 
16.        Calendar cal; 
17.        for (int i = 1; i < 100; i++) { 
18.            System.out.println("no." + i); 
19.            year++; 
20.            cal = Calendar.getInstance(); 
21.            cal.set(Calendar.YEAR, year); 
22.            //Date date = cal.getTime(); 
23.            //dateUtil.format(date); 
24.            dateUtil.parse(year + "-05-25 11:21:21"); 
25.            try { 
26.                Thread.sleep(1); 
27.            } catch (Exception e) { 
28.                e.printStackTrace(); 
29.            } 
30.        } 
31.    } 
32.} 



4.测试主方法



Java代码 
1.package com.bijian.study.date; 
2. 
3.public class DateMainTest { 
4. 
5.    public static void main(String[] args) { 
6.         
7.        DateUtilInterface dateUtil = new DateUtil01(); 
8.        Runnable runabble = new DateThread(dateUtil); 
9.        for(int i=0;i<10;i++){ 
10.            new Thread(runabble).start(); 
11.        } 
12.    } 
13.} 



运行结果:



Text代码 
1.no.1 
2.no.1 
3.no.1 
4.Fri May 25 11:21:21 CST 2001 
5.Fri May 25 11:21:21 CST 2001 
6.Fri May 25 11:21:21 CST 2001 
7.no.1 
8.no.1 
9.Fri May 25 11:21:21 CST 2001 
10.Fri May 25 11:21:21 CST 2001 
11.no.1 
12.no.1 
13.Fri May 25 11:21:21 CST 2001 
14.no.1 
15.Fri May 25 11:21:21 CST 2001 
16.no.1 
17.Fri May 25 11:00:21 CST 2001 
18.Wed Sep 25 11:21:21 CST 2002 
19.no.1 
20.no.2 
21.no.2 
22.Sat May 25 11:21:21 CST 2002 
23.no.2 
24.no.2 
25.no.2 
26.Sat May 25 11:21:21 CST 2002 
27.no.2 
28.Sat May 25 11:21:21 CST 2002 
29.Fri May 25 11:21:21 CST 2001 
30.Sat May 25 11:21:21 CST 2002 
31.no.2 
32.Sat May 25 11:21:21 CST 2002 
33.no.2 
34.Sat May 25 11:21:21 CST 2002 
35.no.2 
36.Sat May 25 11:21:21 CST 2002 
37.Exception in thread "Thread-2" java.lang.NumberFormatException: For input string: ".00221E.00221E4" 
38.    at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
39.    at java.lang.Double.parseDouble(Unknown Source) 
40.    at java.text.DigitList.getDouble(Unknown Source) 
41.    at java.text.DecimalFormat.parse(Unknown Source) 
42.    at java.text.SimpleDateFormat.subParse(Unknown Source) 
43.    at java.text.SimpleDateFormat.parse(Unknown Source) 
44.    at java.text.DateFormat.parse(Unknown Source) 
45.    at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19) 
46.    at com.bijian.study.date.DateThread.run(DateThread.java:24) 
47.    at java.lang.Thread.run(Unknown Source) 
48.Exception in thread "Thread-5" java.lang.NumberFormatException: For input string: ".00221E.00221E44" 
49.    at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
50.    at java.lang.Double.parseDouble(Unknown Source) 
51.    at java.text.DigitList.getDouble(Unknown Source) 
52.    at java.text.DecimalFormat.parse(Unknown Source) 
53.    at java.text.SimpleDateFormat.subParse(Unknown Source) 
54.    at java.text.SimpleDateFormat.parse(Unknown Source) 
55.    at java.text.DateFormat.parse(Unknown Source) 
56.    at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19) 
57.    at com.bijian.study.date.DateThread.run(DateThread.java:24) 
58.    at java.lang.Thread.run(Unknown Source) 
59.no.3 
60.no.3 
61.Sun May 25 11:21:21 CST 2003 
62.no.3 
63.no.3 
64.no.3 
65.Sun May 25 11:21:21 CST 2003 
66.no.4 
67.Sun May 25 11:21:21 CST 2003 
68.no.3 
69.Tue May 25 11:21:21 CST 2004 
70.no.2 
71.Sun May 25 11:21:21 CST 2003 
72.no.3 
73.Thu Jan 01 00:21:21 CST 1970 
74.Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "E212" 
75.    at java.lang.NumberFormatException.forInputString(Unknown Source) 
76.    at java.lang.Long.parseLong(Unknown Source) 
77.    at java.lang.Long.parseLong(Unknown Source) 
78.    at java.text.DigitList.getLong(Unknown Source) 
79.    at java.text.DecimalFormat.parse(Unknown Source) 
80.    at java.text.SimpleDateFormat.subParse(Unknown Source) 
81.    at java.text.SimpleDateFormat.parse(Unknown Source) 
82.    at java.text.DateFormat.parse(Unknown Source) 
83.    at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19) 
84.    at com.bijian.study.date.DateThread.run(DateThread.java:24) 
85.    at java.lang.Thread.run(Unknown Source) 
86.java.lang.NumberFormatException: For input string: "E212" 
87.    at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
88.    at java.lang.Double.parseDouble(Unknown Source) 
89.    at java.text.DigitList.getDouble(Unknown Source) 
90.    at java.text.DecimalFormat.parse(Unknown Source) 
91.    at java.text.SimpleDateFormat.subParse(Unknown Source) 
92.    at java.text.SimpleDateFormat.parse(Unknown Source) 
93.    at java.text.DateFormat.parse(Unknown Source) 
94.    at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19) 
95.    at com.bijian.study.date.DateThread.run(DateThread.java:24) 
96.    at java.lang.Thread.run(Unknown Source) 
97.Exception in thread "Thread-8" java.lang.NumberFormatException: For input string: "" 
98.    at java.lang.NumberFormatException.forInputString(Unknown Source) 
99.    at java.lang.Long.parseLong(Unknown Source) 
100.    at java.lang.Long.parseLong(Unknown Source) 
101.    at java.text.DigitList.getLong(Unknown Source) 
102.    at java.text.DecimalFormat.parse(Unknown Source) 
103.    at java.text.SimpleDateFormat.subParse(Unknown Source) 
104.    at java.text.SimpleDateFormat.parse(Unknown Source) 
105.    at java.text.DateFormat.parse(Unknown Source) 
106.    at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19) 
107.    at com.bijian.study.date.DateThread.run(DateThread.java:24) 
108.    at java.lang.Thread.run(Unknown Source) 
109.no.4 
110.no.4 
111.... 
112.... 
113.... 



      从如上运行结果来看,SimpleDateFormat的parse方法有线程安全问题。

   修改调用日期工具的线程类如下,测试SimpleDateFormat的format方法是否有线程安全问题:



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.util.Calendar; 
4.import java.util.Date; 
5. 
6.public class DateThread implements Runnable { 
7. 
8.    DateUtilInterface dateUtil = null; 
9. 
10.    public DateThread(DateUtilInterface dateUtil) { 
11.        this.dateUtil = dateUtil; 
12.    } 
13. 
14.    public void run() { 
15.        int year = 2000; 
16.        Calendar cal; 
17.        for (int i = 1; i < 100; i++) { 
18.            System.out.println("no." + i); 
19.            year++; 
20.            cal = Calendar.getInstance(); 
21.            cal.set(Calendar.YEAR, year); 
22.            Date date = cal.getTime(); 
23.            dateUtil.format(date); 
24.            //dateUtil.parse(year + "-05-25 11:21:21"); 
25.            try { 
26.                Thread.sleep(1); 
27.            } catch (Exception e) { 
28.                e.printStackTrace(); 
29.            } 
30.        } 
31.    } 
32.} 

运行结果:



Text代码 
1.no.1 
2.no.1 
3.2001-05-22 13:07:22 
4.2001-05-22 13:07:22 
5.no.1 
6.no.1 
7.2001-05-22 13:07:22 
8.2001-05-22 13:07:22 
9.no.1 
10.2001-05-22 13:07:22 
11.no.1 
12.no.1 
13.2001-05-22 13:07:22 
14.2001-05-22 13:07:22 
15.no.1 
16.no.1 
17.2001-05-22 13:07:22 
18.2001-05-22 13:07:22 
19.no.1 
20.2001-05-22 13:07:22 
21.no.2 
22.no.2 
23.no.2 
24.no.2 
25.2002-05-22 13:07:22 
26.no.2 
27.2002-05-22 13:07:22 
28.2002-05-22 13:07:22 
29.no.2 
30.2002-05-22 13:07:22 
31.no.2 
32.... 
33.... 
34.... 

    多次运行,均未出现异常,因此个人预测,SimpleDateFormat的format方法没有线程安全的问题。

     

        有三种方法可以解决以上安全问题。
  1).使用同步



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.text.ParseException; 
4.import java.text.SimpleDateFormat; 
5.import java.util.Date; 
6. 
7.public class DateUtil02 implements DateUtilInterface { 
8. 
9.    private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
10.     
11.    @Override 
12.    public void format(Date date) { 
13.        System.out.println(dateformat.format(date)); 
14.    } 
15. 
16.    @Override 
17.    public void parse(String str) { 
18.        try { 
19.            synchronized(dateformat){ 
20.                System.out.println(dateformat.parse(str)); 
21.            } 
22.        } catch (ParseException e) { 
23.            e.printStackTrace(); 
24.        } 
25.    } 
26.} 

修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil01();为DateUtilInterface dateUtil = new DateUtil02();测试OK。

        不过,当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,这样的操作也会一定程度上影响性能。



  2).每次使用时,都创建一个新的SimpleDateFormat实例



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.text.ParseException; 
4.import java.text.SimpleDateFormat; 
5.import java.util.Date; 
6. 
7.public class DateUtil03 implements DateUtilInterface { 
8. 
9.    @Override 
10.    public void format(Date date) { 
11.         
12.        SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
13.        System.out.println(dateformat.format(date)); 
14.    } 
15. 
16.    @Override 
17.    public void parse(String str) { 
18.        try { 
19.            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
20.            System.out.println(dateformat.parse(str)); 
21.        } catch (ParseException e) { 
22.            e.printStackTrace(); 
23.        } 
24.    } 
25.} 

修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil02();为DateUtilInterface dateUtil = new DateUtil03();测试OK。

 

  3).借助ThreadLocal对象每个线程只创建一个实例

     借助ThreadLocal对象每个线程只创建一个实例,这是推荐的方法

     对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.text.DateFormat; 
4.import java.text.ParseException; 
5.import java.text.SimpleDateFormat; 
6.import java.util.Date; 
7. 
8.public class DateUtil04 implements DateUtilInterface { 
9. 
10.    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 
11. 
12.    // 第一次调用get将返回null 
13.    private static ThreadLocal threadLocal = new ThreadLocal(){ 
14.        protected Object initialValue() {   
15.            return null;//直接返回null   
16.        }  
17.    }; 
18.     
19.    // 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中 
20.    public static DateFormat getDateFormat() { 
21.        DateFormat df = (DateFormat) threadLocal.get(); 
22.        if (df == null) { 
23.            df = new SimpleDateFormat(DATE_FORMAT); 
24.            threadLocal.set(df); 
25.        } 
26.        return df; 
27.    } 
28. 
29.    @Override 
30.    public void parse(String textDate) { 
31. 
32.        try { 
33.            System.out.println(getDateFormat().parse(textDate)); 
34.        } catch (ParseException e) { 
35.            e.printStackTrace(); 
36.        } 
37.    } 
38. 
39.    @Override 
40.    public void format(Date date) { 
41.        System.out.println(getDateFormat().format(date)); 
42.    } 
43.} 

创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。

         也可以采用下面方式创建。



Java代码 
1.package com.bijian.study.date; 
2. 
3.import java.text.DateFormat; 
4.import java.text.ParseException; 
5.import java.text.SimpleDateFormat; 
6.import java.util.Date; 
7. 
8.public class DateUtil05 implements DateUtilInterface { 
9. 
10.    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 
11.     
12.    @SuppressWarnings("rawtypes") 
13.    private static ThreadLocal threadLocal = new ThreadLocal() { 
14.        protected synchronized Object initialValue() { 
15.            return new SimpleDateFormat(DATE_FORMAT); 
16.        } 
17.    }; 
18. 
19.    public DateFormat getDateFormat() { 
20.        return (DateFormat) threadLocal.get(); 
21.    } 
22. 
23.    @Override 
24.    public void parse(String textDate) { 
25. 
26.        try { 
27.            System.out.println(getDateFormat().parse(textDate)); 
28.        } catch (ParseException e) { 
29.            e.printStackTrace(); 
30.        } 
31.    } 
32. 
33.    @Override 
34.    public void format(Date date) { 
35.        System.out.println(getDateFormat().format(date)); 
36.    } 
37.} 

    修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil03();为DateUtilInterface dateUtil = new DateUtil04();或者DateUtilInterface dateUtil = new DateUtil05();测试OK。



      最后,当然也可以使用apache commons-lang包的DateFormatUtils或者FastDateFormat实现,apache保证是线程安全的,并且更高效。



     附:Oracle官方bug说明: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6178997

你可能感兴趣的:(-解决SimpleDateFormat的线程不安全问题的方法)