點擊左上角藍字,關注「鍋外的大佬」
事實證明,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 中線程安全的日期時間 APIJava 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即可領取