SimpleDateFormat线程不安全(转)

本文介绍了三种确保日期格式化线程安全的方法:同步、每次使用新建实例及使用ThreadLocal对象。并提供了具体代码实现。

 

 

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

 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实例

 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变量的拷贝或者叫做副本,代码如下:

 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方法,主要作用是创建时初始化实例。

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

 

 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

 

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

 

 

 

转载于:https://www.cnblogs.com/SamuelSun/p/4064256.html

### SimpleDateFormat线程安全的原因 `SimpleDateFormat` 类本身线程安全的,其内部维护了一个 `Calendar` 实现对象用于解析和格式化日期[^1]。当多个线程同时访问同一个 `SimpleDateFormat` 实例时,可能会因为共享状态而导致可预测的结果。例如,在多线程环境下调用 `format()` 或 `parse()` 方法可能导致数据覆盖或异常行为。 --- ### 解决方案一:使用同步机制(Synchronized) 可以通过加锁的方式确保同一时间只有一个线程能够操作 `SimpleDateFormat` 实例。以下是基于 synchronized 锁的一个解决方案: ```java import java.text.SimpleDateFormat; import java.util.Date; public class ThreadSafeDateFormatter { private static final Object lock = new Object(); private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date) { synchronized (lock) { return sdf.format(date); } } public static Date parseDate(String str) throws Exception { synchronized (lock) { return sdf.parse(str); } } } ``` 上述代码通过引入一个全局锁来保护对 `SimpleDateFormat` 的访问,从而解决了线程安全性问题。 --- ### 解决方案二:每次创建新的实例 另一种方式是在每次使用时都创建一个新的 `SimpleDateFormat` 实例。这种方式虽然简单,但由于频繁创建对象会增加内存开销,因此性能较低。适合在单线程环境中或者偶尔使用的场景下采用。 ```java public static String formatDate(Date date, String pattern) { return new SimpleDateFormat(pattern).format(date); // 每次新建实例 } ``` 这种方法避免了线程竞争的问题,但也带来了额外的对象分配成本[^4]。 --- ### 解决方案三:使用ThreadLocal存储独立副本 利用 `ThreadLocal` 可以为每个线程提供单独的 `SimpleDateFormat` 副本,这样可以有效避免线程间的干扰。 ```java import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; public class ThreadLocalDateFormatter { private static final ConcurrentHashMap<String, ThreadLocal<SimpleDateFormat>> formatters = new ConcurrentHashMap<>(); public static String formatDate(Date date, String pattern) { ThreadLocal<SimpleDateFormat> formatter = formatters.computeIfAbsent( pattern, key -> ThreadLocal.withInitial(() -> new SimpleDateFormat(key)) ); return formatter.get().format(date); } } ``` 此方法结合了缓存技术和线程隔离特性,既提高了效率又保证了线程安全[^2]。 --- ### 替代方案:使用Java 8中的DateTimeFormatter 从 Java 8 开始推荐使用 `DateTimeFormatter` 来替代 `SimpleDateFormat`,因为它是一个可变类并设计为线程安全的。下面展示了如何使用它完成类似的日期处理功能。 ```java import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class SafeDateFormatter { private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static String format(LocalDateTime ldt) { return dtf.format(ldt); } public static LocalDateTime parse(String text) { return LocalDateTime.parse(text, dtf); } } ``` 相比传统的 `SimpleDateFormat`,`DateTimeFormatter` 仅更高效而且无需担心线程安全问题[^3]。 --- ### 总结 针对 `SimpleDateFormat` 线程安全的情况,可以选择以下几种策略之一: - 使用同步机制; - 每次创建新实例; - 利用 `ThreadLocal` 提供每线程独占资源; - 迁移到现代 API 如 `DateTimeFormatter`。 具体选择取决于实际需求以及项目环境下的权衡考量。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值