(一)引子
最近看公司同事写的日期格式化代码:
public static String formatDate(Date date)throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(date); }
就想起之前所学到的,SimpleDateFormat是线程不安全的。
此处的代码也印证了之前所学,所以同事的代码每次都新new一个SimpleDateFormat。
虽然不高明,但杜绝了安全隐患。
更甚一步,觉得自己应该详细读下其源码,理解它在哪一点上不安全。
(二)如何不安全
看format的源码,一段代码跃然纸上:
1 private StringBuffer format(Date date, StringBuffer toAppendTo, 2 FieldDelegate delegate) { 3 // Convert input date to time field list 4 calendar.setTime(date); 5 6 boolean useDateFormatSymbols = useDateFormatSymbols(); 7 8 for (int i = 0; i < compiledPattern.length; ) { 9 int tag = compiledPattern[i] >>> 8; 10 int count = compiledPattern[i++] & 0xff; 11 if (count == 255) { 12 count = compiledPattern[i++] << 16; 13 count |= compiledPattern[i++]; 14 } 15 16 switch (tag) { 17 case TAG_QUOTE_ASCII_CHAR: 18 toAppendTo.append((char)count); 19 break; 20 21 case TAG_QUOTE_CHARS: 22 toAppendTo.append(compiledPattern, i, count); 23 i += count; 24 break; 25 26 default: 27 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); 28 break; 29 } 30 } 31 return toAppendTo; 32 }
第四行代码 calendar.setTime(date) 缓存了时间在成员变量calendar中,而subFormat又使用了calendar。
所以多个线程调用SimpleDateFormat时,共用变量calendar,互相影响。
(三)验证下其不安全
验证代码如下——
package com.concurrent; import java.text.ParseException; import java.util.Date; /** * Created by baimq on 2018/1/10. */ public class SimpleDateFormatTest { public static class TestSimpleDateFormatThreadSafe extends Thread { @Override public void run() { while(true) { try { this.join(2000); } catch (InterruptedException e1) { e1.printStackTrace(); } try { System.out.println(this.getName()+":"+new Date()+":"+ DateUtil.parse("2018-01-10 22:00:20")); } catch (ParseException e) { e.printStackTrace(); } } } } public static void main(String[] args) { for(int i = 0; i < 3; i++){ new TestSimpleDateFormatThreadSafe().start(); } } }
(四)如何安全
(1)每次使用时new一个SimpleDateFormat
好处是线程安全,代码简单
坏处是开销大
(2)使用theadlocal
其原理是,每次使用时复制一份SimpleDateFormat,所以不会有线程安全问题
代码如下——
private static ThreadLocal<SimpleDateFormat> threadLocalSDF = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static String formatDate(Date date)throws ParseException{ return threadLocalSDF.get().format(date); }
(3)在博客园里还见到其它方案
1.使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析。
2.使用Joda-Time类库来处理时间相关问题