今天一位優秀的架構師告訴我,下面這段程式碼SimpleDateFormat是執行緒不安全的。
/** * 將Date按格式轉化成String * * @param date Date物件 * @param pattern 日期型別 * @return String */ public static String date2String(Date date, String pattern) { if (date == null || pattern == null) { return null; } return new SimpleDateFormat(pattern).format(date); }
那麼let us test!
簡單介紹下我的測試方法
1.時間轉字串
2.字串轉時間
3.時間轉字串
比較第一個字串和第二個字元是否相同。如果沒有併發問題,那麼第一個字串跟第二個字串肯定完全一樣
第一種情況
一個SimpleDateFormat例項,併發執行。
private static void newSimpleDate() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000)); while (true) { poolExecutor.execute(new Runnable() { @Override public void run() { String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); if (!dateString.equals(dateString2)) { System.out.println(dateString.equals(dateString2)); } } catch (ParseException e) { e.printStackTrace(); } } }); }
結果:
false false false false false false false
說明存線上程不安全的問題。
第二種情況
每次新建一個SimpleDateFormat 物件
private static void oneSimpleDate() { ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000)); while (true) { poolExecutor.execute(new Runnable() { @Override public void run() { try { date2String(new Date(), "yyyy-MM-dd HH:mm:ss"); } catch (ParseException e) { e.printStackTrace(); } } }); } }
public static void date2String(Date date, String pattern) throws ParseException { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); String dateString = new SimpleDateFormat(pattern).format(date); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(dateString.equals(dateString2)); }
結果:永遠為true
true true true true true true true
說明沒有執行緒安全問題。
奇怪,那麼SimpleDateFormat究竟有沒有問題呢?
簡單看一下SimpleDateFormat.format()方法。
protected Calendar calendar;
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { pos.beginIndex = pos.endIndex = 0; return format(date, toAppendTo, pos.getFieldDelegate()); } // Called from Format after creating a FieldDelegate private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) { int tag = compiledPattern[i] >>> 8; int count = compiledPattern[i++] & 0xff; if (count == 255) { count = compiledPattern[i++] << 16; count |= compiledPattern[i++]; } switch (tag) { case TAG_QUOTE_ASCII_CHAR: toAppendTo.append((char)count); break; case TAG_QUOTE_CHARS: toAppendTo.append(compiledPattern, i, count); i += count; break; default: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); break; } } return toAppendTo; }
可以看到,多個執行緒之間共享變數calendar,並修改calendar。因此在多執行緒環境下,當多個執行緒同時使用相同的SimpleDateFormat物件(如static修飾)的話,如呼叫format方法時,多個執行緒會同時呼叫calender.setTime方法,導致time被別的執行緒修改,因此執行緒是不安全的。此外,parse方法也是執行緒不安全的。
SimpleDateFormat不是執行緒安全的,但這並不代表,它無法被執行緒安全的使用,當你把它作為區域性變數,每次新建一個例項,或者加鎖,或者採用架構師說的DateTimeFormatter都能規避這個問題。
就像hashmap,大家都知道他是執行緒不安全的,在jdk1.7採用頭插法時,併發會出現死迴圈。但是你每次new hashmap物件,去put肯定不會有問題,儘管不會有人這麼用,業務也不允許。
所以,這件事告訴我們,不要守著自己的教條主義,看到執行緒不安全的類,就覺得方法是執行緒不安全的,要看具體的使用場景;當然執行緒安全的類,也有可能是執行緒不安全的;架構師也不例外喔!