還在使用SimpleDateFormat?你的專案崩沒?
文字公眾號來源:Felix周(id:felix_space) 作者:Felix
一篇關於SimpleDateFormat乾貨分享給大家!
日常開發中,我們經常需要使用時間相關類,說到時間相關類,想必大家對SimpleDateFormat並不陌生。主要是用它進行時間的格式化輸出和解析,挺方便快捷的,但是SimpleDateFormat並不是一個執行緒安全的類。在多執行緒情況下,會出現異常,想必有經驗的小夥伴也遇到過。下面我們就來分析分析SimpleDateFormat為什麼不安全?是怎麼引發的?以及多執行緒下有那些SimpleDateFormat的解決方案?
先看看《阿里巴巴開發手冊》對於SimpleDateFormat是怎麼看待的:
二 問題場景復現
一般我們使用SimpleDateFormat的時候會把它定義為一個靜態變數,避免頻繁建立它的物件例項,如下程式碼:
public class SimpleDateFormatTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) throws ParseException {
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
public static void main(String[] args) throws InterruptedException, ParseException {
System.out.println(sdf.format(new Date()));
}
}
是不是感覺沒什麼毛病?單執行緒下自然沒毛病了,都是運用到多執行緒下就有大問題了。 測試下:
public static void main(String[] args) throws InterruptedException, ParseException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int j = 0; j < 10; j++) {
try {
System.out.println(parse("2018-01-02 09:45:59"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
}
// 等待上述的執行緒執行完
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
}
控制檯列印結果:
你看這不崩了?部分執行緒獲取的時間不對,部分執行緒直接報 java.lang.NumberFormatException:multiple points錯,執行緒直接掛死了。
三 多執行緒不安全原因
因為我們把SimpleDateFormat定義為靜態變數,那麼多執行緒下SimpleDateFormat的例項就會被多個執行緒共享,B執行緒會讀取到A執行緒的時間,就會出現時間差異和其它各種問題。SimpleDateFormat和它繼承的DateFormat類也不是執行緒安全的
來看看SimpleDateFormat的format()方法的原始碼
// 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.setTime(date),SimpleDateFormat的format方法實際操作的就是Calendar。
因為我們宣告SimpleDateFormat為static變數,那麼它的Calendar變數也就是一個共享變數,可以被多個執行緒訪問。
假設執行緒A執行完calendar.setTime(date),把時間設定成2019-01-02,這時候被掛起,執行緒B獲得CPU執行權。執行緒B也執行到了calendar.setTime(date),把時間設定為2019-01-03。執行緒掛起,執行緒A繼續走,calendar還會被繼續使用(subFormat方法),而這時calendar用的是執行緒B設定的值了,而這就是引發問題的根源,出現時間不對,執行緒掛死等等。
其實SimpleDateFormat原始碼上作者也給過我們提示:
* Date formats are not synchronized.* It is recommended to create separate format instances for each thread.* If multiple threads access a format concurrently, it must be synchronized* externally.
意思就是
日期格式不同步。
建議為每個執行緒建立單獨的格式例項。
如果多個執行緒同時訪問一種格式,則必須在外部同步該格式。
四 解決方案
只在需要的時候建立新例項,不用static修飾
public static String formatDate(Date date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
如上程式碼,僅在需要用到的地方建立一個新的例項,就沒有執行緒安全問題,不過也加重了建立物件的負擔,會頻繁地建立和銷燬物件,效率較低。
synchronized大法好
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) throws ParseException {
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException {
synchronized(sdf){
return sdf.parse(strDate);
}
}
簡單粗暴,synchronized往上一套也可以解決執行緒安全問題,缺點自然就是併發量大的時候會對效能有影響,執行緒阻塞。
ThreadLocal
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
ThreadLocal可以確保每個執行緒都可以得到單獨的一個SimpleDateFormat的物件,那麼自然也就不存在競爭問題了。
基於JDK1.8的DateTimeFormatter
也是《阿里巴巴開發手冊》給我們的解決方案,對之前的程式碼進行改造:
public class SimpleDateFormatTest {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String formatDate2(LocalDateTime date) {
return formatter.format(date);
}
public static LocalDateTime parse2(String dateNow) {
return LocalDateTime.parse(dateNow, formatter);
}
public static void main(String[] args) throws InterruptedException, ParseException {
ExecutorService service = Executors.newFixedThreadPool(100);
// 20個執行緒
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int j = 0; j < 10; j++) {
try {
System.out.println(parse2(formatDate2(LocalDateTime.now())));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 等待上述的執行緒執行完
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
}
}
執行結果就不貼了,不會出現報錯和時間不準確的問題。
DateTimeFormatter原始碼上作者也加註釋說明了,他的類是不可變的,並且是執行緒安全的。
* This class is immutable and thread-safe.
OK,現在是不是可以對你專案裡的日期工具類進行一波最佳化了呢?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900354/viewspace-2629912/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 還用foreach在MyBatis批量插入資料,伺服器還沒崩?MyBatis伺服器
- 沒了IDE,你的Java專案還能Run起來嗎~IDEJava
- 在專案中應該使用Boolean還是使用boolean?Boolean
- 你還在為沒有專案做而煩惱嗎?有哪些值得推薦的 Java 練手專案(第二天)Java
- 你竟然沒用 Maven 構建專案?Maven
- SimpleDateFormat日期格式轉換的使用ORM
- 你的專案使用Optional了嗎?
- 使用 TypeScript 開發你的專案TypeScript
- 【看雪眾測】又有新專案上線啦!你還在等什麼?
- 你的網站還沒用上 HTTPS 嗎網站HTTP
- 使用Mkdocs構建你的專案文件
- 你的專案使用的是哪種配置檔案?
- 沒頭沒尾--專案開發筆記:UML,IDEF在我們專案中的失敗應用 (轉)筆記IDE
- 你還在為創業專案而苦惱?藏“金”萬億的藍海市場就在你面前!創業
- JWT 在專案中的實際使用JWT
- [譯] 為什麼我還沒 Fix 你的 Issue
- Kotlin + MVP + Flutter ,讓你可以在自己的專案中整合 Flutter 並使用KotlinMVPFlutter
- 使用 Laravel Page Speed 優化你的專案Laravel優化
- 在Laravel專案中使用ElasticsearchLaravelElasticsearch
- 在 Flutter 專案中使用 MQTTFlutterMQQT
- celery 在django專案中使用Django
- 在 Maven 專案中使用 HanLPMavenHanLP
- 在vue專案中使用elementUIVueUI
- 專案實戰之gradle在實際專案中的使用Gradle
- 專案分享一:在專案中使用 IScroll 所碰到的那些坑
- 圖形化還原崩潰地址 iOS的crash檔案分析iOS
- Go語言 | 你還在這樣獲取檔案的大小嗎?Go
- TypeScript在React專案中的使用總結TypeScriptReact
- 在非 laravel 專案中使用 laravel 的特性Laravel
- 優雅的在React專案中使用ReduxReactRedux
- 在vue專案中優雅的使用SvgVueSVG
- bing Map 在vue專案中的使用Vue
- Java Web之MySQL在專案中的使用JavaWebMySql
- SAP專案裡的關鍵使用者,兼職還是專職?
- 你是在授權,還是在逼走你的人才?
- 專案實戰之Rxjava、RxBinding在實際專案中的使用RxJava
- 使用 ndb 除錯你的 Node.js 專案除錯Node.js
- 在vue專案中使用骨架屏Vue