不簡單的 Java SimpleDateFormat

2021-02-23 鍋外的大佬

點擊左上角藍字,關注「鍋外的大佬」

事實證明,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));}
// Fri Jan 11 00:00:00 IST 2019

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

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:當我們使用實例變量時,應始終檢查其是否是一個線程安全類。

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

1. 方案一: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); }
}

2. 方案二: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!

spring for all翻譯組

8月福利來襲!關注公眾號

後臺回復003 ,領取7月翻譯集錦~ 

往期福利回復001, 002即可領取  

相關焦點

  • Java SimpleDateFormat 沒那麼簡單
    (給ImportNew加星標,提高Java技能)編譯:ImportNew/唐尤華dzone.com/articles/java-simpledateformat-is-not-simple
  • 不簡單的 SimpleDateFormat
    事實證明,Java 的 SimpleDateFormat 並沒有那麼簡單。格式化和解析日期是個(痛苦的)日常任務。每天,它都讓我們很頭疼。在 Java 中格式化和解析日期的一種常見方法是使用 SimpleDateFormat。下面是我們用到的一個公共類。
  • 為什麼SimpleDateFormat不是線程安全的?
    It is recommended to create separate format instances for each thread.If multiple threads access a format concurrently, it must be synchronizedexternally.日期格式不同步。
  • SimpleDateFormat 如何安全的使用?
    想必大家對 SimpleDateFormat 並不陌生。SimpleDateFormat 是 Java 中一個非常常用的類,他是以區域敏感的方式格式化和解析日期的具體類。 它允許格式化 (date -> text)、語法分析 (text -> date)和標準化。SimpleDateFormat 允許以任何用戶指定的日期-時間格式方式啟動。
  • 2020 年,你還在使用 Java 中的 SimpleDateFormat 嗎?
    datestr = df.format(date);  System.out.println(datestr);最終輸出的時間為由於在java 8之前 SimpleDateFormat 是一個比較常用的類,但是我還是在這裡要建議開發者不要用 SimpleDateForma。
  • 還在使用SimpleDateFormat?
    直接崩了,部分線程獲取的時間不對,部分線程報java.lang.NumberFormatException:multiple points錯,線程直接掛死了。還有部分線程報empty String錯,值有問題。
  • 還在使用 SimpleDateFormat?你的項目崩沒?
    你看這不崩了?部分線程獲取的時間不對,部分線程直接報java.lang.NumberFormatException: multiple points錯,線程直接掛死了。}簡單粗暴,synchronized往上一套也可以解決線程安全問題,缺點自然就是並發量大的時候會對性能有影響,線程阻塞。
  • 【107期】SimpleDateFormat 的線程安全問題與解決方案
    原因 SimpleDateFormat(下面簡稱sdf)類內部有一個Calendar對象引用,它用來儲存和這個sdf相關的日期信息,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法參數傳入的日期相關String, Date等等, 都是交友Calendar引用來儲存的.
  • 還在使用SimpleDateFormat?你的項目崩沒?
    你看這不崩了?部分線程獲取的時間不對,部分線程直接報 java.lang.NumberFormatException:multiple points錯,線程直接掛死了。(sdf){            return sdf.parse(strDate);        }    }簡單粗暴,synchronized往上一套也可以解決線程安全問題,缺點自然就是並發量大的時候會對性能有影響,線程阻塞。
  • 筆記總結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?
    直接崩了,部分線程獲取的時間不對,部分線程報java.lang.NumberFormatException:multiple points錯,線程直接掛死了。還有部分線程報empty String錯,值有問題。
  • 你知道 SimpleDateFormat 的性能問題怎麼解決嗎?
    背景大家都知道,SimpleDateFormat 可以把 java.util.Date 對象或類似
  • Java 8 日期 / 時間( Date Time )API 指南
    java.time.chrono包:這個包為非ISO的日曆系統定義了一些泛化的API,我們可以擴展AbstractChronology類來創建自己的日曆系統。java.time.format包:這個包包含能夠格式化和解析日期時間對象的類,在絕大多數情況下,我們不應該直接使用它們,因為java.time包中相應的類已經提供了格式化和解析的方法。
  • 阿里規定代碼中禁用static修飾SimpleDateFormat,為何?
    ,但SimpleDateFormat是線程不安全的SimpleDateFormat的format方法最終調用代碼:private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate)
  • 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
  • Java 8-如何轉換LocalDate為Date
    這是相同的代碼:Date public static Date convertToDateUsingInstant(LocalDate date) {       return java.util.Date.from
  • 面試官一步一步的套路你,為什麼SimpleDateFormat不是線程安全的
    小小白:如果不使用ThreadLocal包裝一下,直接創建一個SimpleDateFormat共享實例對象,在多線程並發的情況下使用這個對象的方法是線程不安全的,可能會拋出NumberFormatException或其它異常。
  • 別用 Date 了,使用 Java8 日期處理的新特性,真香!
    不同於老版本,新API基於ISO標準日曆系統,java.time包下的所有類都是不可變類型而且線程安全。關鍵類LocalDate:本地日期,不包含具體時間 例如:2014-01-14 可以用來記錄生日、紀念日、加盟日等。LocalDateTime:組合了日期和時間,但不包含時差和時區信息。ZonedDateTime:最完整的日期時間,包含時區和相對UTC或格林威治的時差。
  • 【問答】MySQL DATE_FORMAT函數怎麼用?
    在我們平常使用MySQL時,有可能會對某些日期數據進行格式化,使它變為我們想要的格式,此時我們就會使用 DATE_FORMAT(date,format) 函數。年-月-日 的形式(格式)展示,那麼它格式化之後就是 2020-11-25DATE_FORMAT() 接收兩個參數:date
  • java中日期格式化的大坑!
    前言我們都知道在java中進行日期格式化使用simpledateformat。