不簡單的 SimpleDateFormat

2021-02-19 程序猿DD

點擊藍色「程序猿DD」關注我

回復「資源」獲取獨家整理的學習資料!

事實證明,Java 的 SimpleDateFormat 並沒有那麼簡單。

格式化和解析日期是個(痛苦的)日常任務。每天,它都讓我們很頭疼。

在 Java 中格式化和解析日期的一種常見方法是使用 SimpleDateFormat。下面是我們用到的一個公共類。

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;
public final class DateUtils {
public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private DateUtils() {}
public static Date parse(String target) { try { return SIMPLE_DATE_FORMAT.parse(target); } catch (ParseException e) { e.printStackTrace(); } return null; }
public static String format(Date target) { return SIMPLE_DATE_FORMAT.format(target); }
}

你覺得它會像我們預期的那樣進行工作麼?讓我們試一試。

private static void testSimpleDateFormatInSingleThread() {    final String source = "2019-01-11";    System.out.println(DateUtils.parse(source));}

是的,它奏效了。接下來再用多線程再試一試。

private static void testSimpleDateFormatWithThreads() {    ExecutorService executorService = Executors.newFixedThreadPool(10);
final String source = "2019-01-11";
System.out.println(":: parsing date string ::"); IntStream.rangeClosed(0, 20) .forEach((i) -> executorService.submit(() -> System.out.println(DateUtils.parse(source))));
executorService.shutdown();}

這是我得到的結果:

:: parsing date string ::... omittedFri Jan 11 00:00:00 IST 2019Sat Jul 11 00:00:00 IST 2111Fri Jan 11 00:00:00 IST 2019... omitted

結果很有意思,不是麼?這是我們大多數人在 Java 中格式化日期時常犯的錯誤。為什麼?因為我們不了解線程安全。以下是 Java doc 中關於 SimpleDateFormat 的內容:

日期格式是不同步的。

建議為每個線程創建獨立的格式實例。

如果多個線程同時訪問一個格式,則它必須是外部同步的。

Tip:當我們使用實例變量時,應始終檢查其是否是一個線程安全類。

正如文檔所述,我們為每個線程持有一個獨立的變量來解決該問題。如果我們想共享對象?有什麼解決方案?

方案一:ThreadLocal

這個問題可以通過使用 ThreadLocal 變量來解決。ThreadLocal 的 get() 方法將為我們提供當前線程的正確值。

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;
public final class DateUtilsThreadLocal {
public static final ThreadLocal SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private DateUtilsThreadLocal() {}
public static Date parse(String target) { try { return SIMPLE_DATE_FORMAT.get().parse(target); } catch (ParseException e) { e.printStackTrace(); } return null; }
public static String format(Date target) { return SIMPLE_DATE_FORMAT.get().format(target); }}

方案二:Java 8 中線程安全的日期時間 API

Java 8 引入了一套新的日期時間 API。我們有一個更好的、麻煩更少的 SimpleDateFormat 替代品。如果我們真的需要堅持使用 SimpleDateFormat,可以繼續使用 ThreadLocal。但是當有更好的選擇時,我們應考慮使用它。

Java 8 引入了幾個線程安全的日期類。

以下是 Java doc 的描述:

本類是不可變的,且線程安全的。

這些類是更加值得研究的,包括 DateTimeFormatter,[OffsetDateTime], ZonedDateTime,LocalDateTime ,LocalDate 和 LocalTime。

我們的解決方案:

import java.time.LocalDate;import java.time.format.DateTimeFormatter;
public class DateUtilsJava8 {
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private DateUtilsJava8() {}
public static LocalDate parse(String target) { return LocalDate.parse(target, DATE_TIME_FORMATTER); }
public static String format(LocalDate target) { return target.format(DATE_TIME_FORMATTER); }
}

3. 結論

Java 8 的解決方案使用不可變類,這是解決多線程問題的好方法。不可變類本質上是線程安全的,所以請儘可能地使用它們。

Happy coding!

留言交流不過癮?添加微信:zyc_enjoy

根據指引加入各種主題討論群

籤到計劃

活動介紹:自律到極致-人生才精緻:第13期

活動獎勵:《Spring Cloud微服務:入門、實戰與進階》 x 10

掃描下方二維碼,籤到參與

推薦關注:鍋外的大佬

相關焦點

  • Java SimpleDateFormat 沒那麼簡單
    (給ImportNew加星標,提高Java技能)編譯:ImportNew/唐尤華dzone.com/articles/java-simpledateformat-is-not-simple
  • 為什麼SimpleDateFormat不是線程安全的?
    It is recommended to create separate format instances for each thread.If multiple threads access a format concurrently, it must be synchronizedexternally.日期格式不同步。
  • 不簡單的 Java SimpleDateFormat
    點擊左上角藍字,關注「鍋外的大佬」事實證明,Java 的 SimpleDateFormat 並沒有那麼簡單
  • SimpleDateFormat 如何安全的使用?
    想必大家對 SimpleDateFormat 並不陌生。SimpleDateFormat 是 Java 中一個非常常用的類,他是以區域敏感的方式格式化和解析日期的具體類。 它允許格式化 (date -> text)、語法分析 (text -> date)和標準化。SimpleDateFormat 允許以任何用戶指定的日期-時間格式方式啟動。
  • 筆記總結Date、Calendar、SimpleDateFormat和DecimalFormat
    (long time)//注意:設置的時間為固定不走的時間(定時)Date  date2 = new  Date();date2.setTime(1501179322396L);//Fri Jul 28 02:15:22 CST 2017System.out.println("設置的時間:"+date2);
  • 還在使用SimpleDateFormat?
    來看看SimpleDateFormat的format()方法的源碼:注意, calendar.setTime(date),SimpleDateFormat的format方法實際操作的就是Calendar。
  • 2020 年,你還在使用 Java 中的 SimpleDateFormat 嗎?
    datestr = df.format(date);  System.out.println(datestr);最終輸出的時間為由於在java 8之前 SimpleDateFormat 是一個比較常用的類,但是我還是在這裡要建議開發者不要用 SimpleDateForma。
  • 還在使用 SimpleDateFormat?你的項目崩沒?
    (Date date) throws ParseException {        return sdf.format(date);    }    public static Date parse(String strDate) throws ParseException {        return sdf.parse(strDate);
  • 還在使用SimpleDateFormat?你的項目崩沒?
    (Date date) throws ParseException {        return sdf.format(date);    }    public static Date parse(String strDate) throws ParseException {        return sdf.parse(strDate)
  • 還在使用SimpleDateFormat?
    來看看SimpleDateFormat的format()方法的源碼:注意, calendar.setTime(date),SimpleDateFormat的format方法實際操作的就是Calendar。
  • 【107期】SimpleDateFormat 的線程安全問題與解決方案
    原因 SimpleDateFormat(下面簡稱sdf)類內部有一個Calendar對象引用,它用來儲存和這個sdf相關的日期信息,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法參數傳入的日期相關String, Date等等, 都是交友Calendar引用來儲存的.
  • 阿里規定代碼中禁用static修飾SimpleDateFormat,為何?
    ,但SimpleDateFormat是線程不安全的SimpleDateFormat的format方法最終調用代碼:private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate)
  • 你知道 SimpleDateFormat 的性能問題怎麼解決嗎?
    所以業界優質的解決方案就是緩存 SimpleDateFormat 對象,但是 SimpleDateFormat 卻是線程不安全的,所以考慮到多線程場景,直接使用會造成格式化日期錯誤,因此需要藉助 ThreadLocal 來實現安全的日期格式化。推薦做法下面是推薦你在項目中實現的工具類寫法,可以一定程度提升性能。
  • HGDB兼容MySQL date_format函數
    WHEN n = 'm' THEN pg_catalog.to_char($1, 'MM') WHEN n = 'p' THEN pg_catalog.to_char($1, 'AM') WHEN n = 'r' THEN pg_catalog.to_char($1, 'HH12:MI:SS AM') WHEN n = 'S' THEN pg_catalog.to_char
  • 面試官一步一步的套路你,為什麼SimpleDateFormat不是線程安全的
    小小白:如果不使用ThreadLocal包裝一下,直接創建一個SimpleDateFormat共享實例對象,在多線程並發的情況下使用這個對象的方法是線程不安全的,可能會拋出NumberFormatException或其它異常。
  • 【問答】MySQL DATE_FORMAT函數怎麼用?
    在我們平常使用MySQL時,有可能會對某些日期數據進行格式化,使它變為我們想要的格式,此時我們就會使用 DATE_FORMAT(date,format) 函數。年-月-日 的形式(格式)展示,那麼它格式化之後就是 2020-11-25DATE_FORMAT() 接收兩個參數:date
  • Java 8 日期 / 時間( Date Time )API 指南
    該類提供與java.sql.Date相同的功能,對於如何使用該類,我們來看一個簡單的例子。我們來看一個簡單的例子。我們來看一下簡單的例子。        //default format        System.out.println("Default format of LocalDate="+date);        //specific format        System.out.println(date.format(DateTimeFormatter.ofPattern
  • 應對incaseformat蠕蟲病毒的簡單異構備份方法
    2021年1月13日incaseformat蠕蟲病毒的消息席捲朋友圈,該病毒不具備加密文件危害,同時該病毒也並非新型病毒,而是存在已久,
  • anythingtodate帶你輕鬆處理日期
    其為外部命令,使用前需安裝:ssc install anythingtodate基本語法:anythingtodate varlist [, keepvarlists format(string asis) reference(integer 19000000
  • String.format() 圖文詳解,寫得非常好!
    format()方法有兩種重載形式。(Locale locale, String fmt, Object... args);佔位符格式化說明最多會有5個部分(不包括%符號) .>System.out.print(String.format("十六進位浮點類型:%a %n", num));System.out.print(String.format("通用浮點類型:%g ", num));