SimpleDateFormat定义
1SimpleDateFormat 是一个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。 2 3SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 来创建日期-时间格式器。每一个这样的类方法都能够返回一个以默认格式模式初始化的日期/时间格式器。可以根据需要使用 applyPattern 方法来修改格式模式。
官网同步建议
1同步 2日期格式是不同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须是外部同步的。
为什么线程不安全

上图中,SimpleDateFormat类中,有个对象calendar
**[calendar](http://tool.oschina.net/uploads/apidocs/jdk-zh/java/text/DateFormat.html#calendar)**
DateFormat 使用 calendar 来生成实现日期和时间格式化所需的时间字段值。
当SimpleDateFormat用static申明,多个线程共享SimpleDateFormat对象是,也共享该对象的calendar对象。而当调用parse方法时,会clear所有日历字段和值。当线程A正在调用parse,线程B调用clear,这样解析后的数据就会出现偏差
1//parse方法 2@Override 3public Date parse(String text, ParsePosition pos) 4{ 5 try { 6 parsedDate = calb.establish(calendar).getTime(); 7 ... 8 } 9}
1//establish方法 2Calendar establish(Calendar cal) { 3 ... 4 //将此 Calendar 的所有日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义 5 cal.clear(); 6}
同样 formart中也用到了calendar对象,将date设置到日历的时间字段中
1private StringBuffer format(Date date, StringBuffer toAppendTo, 2 FieldDelegate delegate) { 3 // Convert input date to time field list 4 calendar.setTime(date); 5 ... 6}
当线程A调用setTime,而线程B也调用setTime,这时候线程A最后得到的时间是 最后线程B的时间。也会导致数据偏差
不安全示例:
1public static void main(String[] args) throws InterruptedException { 2 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 3 String dateStr = "1111-11-11 11:11:11"; 4 ExecutorService executorService = Executors.newFixedThreadPool(100); 5 for(int i=0;i<100;i++) { 6 executorService.submit(new Runnable() { 7 @Override 8 public void run() { 9 try { 10 //多个线程操作同一个sdf对象 11 System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); 12 } catch (ParseException e) { 13 System.out.println("--------------> error, " + e.getMessage()); 14 } 15 } 16 }); 17 18 } 19 executorService.shutdown(); 20 }
执行结果:
1... 21111-11-11 11:11:11---pool-1-thread-69 30011-11-11 11:11:11---pool-1-thread-72 40011-11-11 11:11:11---pool-1-thread-71 51111-11-11 11:11:11---pool-1-thread-73 61111-11-11 11:11:11---pool-1-thread-75 71111-11-11 11:11:11---pool-1-thread-76 81111-11-11 11:11:11---pool-1-thread-89 91111-11-11 00:11:11---pool-1-thread-93 101111-11-11 11:11:11---pool-1-thread-96 11...
可以看到数据出现偏差
解决方案
1.为每个实例创建一个单独的SimpleDateFormat对象
1public static void main(String[] args) throws InterruptedException { 2 //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 3 String dateStr = "1111-11-11 11:11:11"; 4 ExecutorService executorService = Executors.newFixedThreadPool(100); 5 for(int i=0;i<100;i++) { 6 executorService.submit(new Runnable() { 7 //为每个线程创建自己的sdf对象 8 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 9 @Override 10 public void run() { 11 try { 12 System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); 13 } catch (ParseException e) { 14 System.out.println("--------------> error, " + e.getMessage()); 15 } 16 } 17 }); 18 19 } 20 executorService.shutdown(); 21 }
缺点:每次new一个实例,都会new一个format对象,虚拟机内存消耗大,垃圾回收频繁
2.给静态SimpleDateFormat对象加锁,使用Lock或者synchronized修饰
1public static void main(String[] args) throws InterruptedException { 2 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 3 String dateStr = "1111-11-11 11:11:11"; 4 ExecutorService executorService = Executors.newFixedThreadPool(100); 5 for(int i=0;i<100;i++) { 6 executorService.submit(new Runnable() { 7 @Override 8 public void run() { 9 //加同步锁 10 synchronized (sdf) { 11 try { 12 System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); 13 } catch (ParseException e) { 14 System.out.println("--------------> error, " + e.getMessage()); 15 } 16 } 17 } 18 }); 19 20 } 21 executorService.shutdown(); 22 }
缺点:性能差,其他线程要等待锁释放
3.使用ThreadLocal为每个线程创建一个SimpleDateFormat对象副本,有线程隔离性,各自的副本对象也不会被其他线程影响
1public static void main(String[] args) throws InterruptedException { 2 //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 3 //初始化threadLocal并设置值 4 ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){ 5 @Override 6 protected SimpleDateFormat initialValue() { 7 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 8 } 9 }; 10 String dateStr = "1111-11-11 11:11:11"; 11 ExecutorService executorService = Executors.newFixedThreadPool(100); 12 for(int i=0;i<100;i++) { 13 executorService.submit(new Runnable() { 14 @Override 15 public void run() { 16 try { 17 System.out.println(threadLocal.get().format(threadLocal.get().parse(dateStr)) + "---" + Thread.currentThread().getName()); 18 } catch (ParseException e) { 19 System.out.println("--------------> error, " + e.getMessage()); 20 } 21 } 22 }); 23 24 } 25 executorService.shutdown(); 26 27 //清理threadLocal,生产环境不清理容易导致内存溢出 threadLocal.remove(); 28 29}
ThreadLocal原理分析