`
lastsoul
  • 浏览: 34905 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

转载-解决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类的线程安全问题和解决方案.docx

    要解决SimpleDateFormat类的线程安全问题,有多种方法可以选择: 1. 使用ThreadLocal: 可以使用ThreadLocal将SimpleDateFormat对象封装在ThreadLocal中,这样每个线程都有自己的SimpleDateFormat对象,从而避免了...

    simpleDateFormat是线程不安全的

    2. **缓存问题**:`SimpleDateFormat`在内部使用了缓存来提高性能,但这个缓存也是线程不安全的。在并发情况下,两个线程同时尝试修改或获取缓存的值,可能导致数据混乱。 3. **解析和格式化操作**:这两个操作不是...

    ThreadLocal:如何优雅的解决SimpleDateFormat多线程安全问题

    目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date日期转String类型(format)SimpleDateFormat出现bug的原因如何解决SimpleDateFormat多线程安全问题局部变量使用...

    SimpleDateFormat线程不安全的5种解决方案.md

    SimpleDateFormat线程不安全的5种解决方案.md

    SimpleDateFormat线程不安全的5种解决方案.docx

    在多线程环境下,SimpleDateFormat 由于其内部状态在格式化过程中可能会被多个线程修改,因此会出现线程不安全的现象。解决这一问题的方法有多种,包括将 SimpleDateFormat 定义为局部变量、使用 synchronized 或 ...

    详解SimpleDateFormat的线程安全问题与解决方案

    本文将深入探讨`SimpleDateFormat`的线程安全问题及其解决方案。 ### 1. 线程安全问题的原因 `SimpleDateFormat`内部维护了一个`Calendar`对象,用于处理日期和时间的解析与格式化。由于`SimpleDateFormat`不是...

    关于SimpleDateFormat的非线程安全问题及其解决方案.docx

    `SimpleDateFormat`类内部维护了一些线程不安全的数据结构,如`Calendar`对象等。当多个线程同时访问并修改这些共享数据结构时,可能会引发数据竞争,从而导致日期格式化或解析过程中的错误。例如,在上文提供的代码...

    深入理解Java:SimpleDateFormat安全的时间格式化

    SimpleDateFormat类不是线程安全的,这意味着在多线程环境下,如果多个线程同时访问同一个SimpleDateFormat实例,可能会导致各种问题,例如转化的时间不正确、报错、线程被挂死等等。 知识点2: 创建...

    Java SimpleDateFormat线程安全问题原理详解

    问题的根源在于SimpleDateFormat的parse方法不是线程安全的。在多线程环境下,如果多个线程同时调用parse方法,可能会导致日期时间格式化结果不正确或抛出异常。 为了解决这个问题,我们可以使用两个方法:使用...

    有关SimpleDateFormat的常用方法说明

    - `SimpleDateFormat`是线程不安全的,如果在多线程环境中使用,需要考虑同步问题。 - 日期和时间的格式化字符串要与实际情况对应,避免出现理解错误。 以上就是关于`SimpleDateFormat`的一些常见用法和注意事项,...

    Java多线程环境下SimpleDateFormat类安全转换

    在示例代码中,我们还定义了一个DateTools类,该类提供了一个静态方法getSimpleDateFormat,该方法返回一个SimpleDateFormat对象,该对象使用ThreadLocal类来绑定,以避免线程安全问题。 5. 线程状态和线程组 在...

    java代码-SimpleDateFormat YYYY解析问题

    在标题提到的"SimpleDateFormat YYYY解析问题"中,通常指的是使用 `YYYY` 作为日期模式时遇到的不预期行为。这个问题主要出现在对年份的处理上。 `SimpleDateFormat` 使用了一些预定义的模式字符,如 `y` 代表年份...

    Java 实例 - 格式化时间SimpleDateFormat使用源代码-详细教程.zip

    `SimpleDateFormat`不是线程安全的,所以在多线程环境中,要么为每个线程创建单独的实例,要么在每次使用后进行同步。 8. **替代方案**: Java 8引入了`java.time`包,其中的`DateTimeFormatter`类提供了更现代且...

    由浅入深解析 SimpleDateFormat

    1. SimpleDateFormat 是线程不安全的,因此在多线程环境下使用需要特别注意。 2. 创建 SimpleDateFormat 实例需要消耗大量的资源,因此应当尽量少创建实例。 3. SimpleDateFormat 可以使用 applyPattern 方法修改...

    DateFormat多线程问题

    它提供了与`SimpleDateFormat`类似的功能,但避免了线程安全问题。 5. **池化`DateFormat`实例**: 尽管`DateFormat`不是线程安全的,但可以通过池化技术减少创建新实例的开销。创建一个`DateFormat`池,线程在...

    java代码-SimpleDateFormat YYYY显示问题

    在描述中提到的问题“java代码-SimpleDateFormat YYYY显示问题”可能指的是在使用`SimpleDateFormat`时遇到了与年份表示相关的异常或者不符合预期的结果。 首先,让我们理解一下`SimpleDateFormat`中的"YYYY"和...

    java SimpleDateFormat &Calendar

    需要注意的是,由于`SimpleDateFormat`不是线程安全的,所以在多线程环境中,建议为每个线程创建单独的实例。 `Calendar`类则是Java中更底层的日期和时间工具,它提供了一套完整的API来操作日期和时间,包括添加、...

    Java ThreadLocal 线程安全问题解决方案

    Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...

    Java多线程编程的线程安全性.docx

    Java标准库中有一些类,如ArrayList、HashMap和SimpleDateFormat,并未设计为线程安全,因此在多线程环境下直接使用可能导致数据不一致或其他问题。开发者应当了解每个类的线程安全特性,以便做出正确的选择和适当地...

Global site tag (gtag.js) - Google Analytics