歡迎光臨
每天分享高質量文章

Java SimpleDateFormat 沒那麼簡單

(給ImportNew加星標,提高Java技能)

編譯:ImportNew/唐尤華

dzone.com/articles/java-simpledateformat-is-not-simple

 

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 ::
... omitted
Fri Jan 11 00:00:00 IST 2019
Sat Jul 11 00:00:00 IST 2111
Fri Jan 11 00:00:00 IST 2019
... omitted

 

結果看上去很奇怪,對吧?這是大多數人用 Java 格式化日期時常犯的一個錯誤。為什麼會有這種奇怪的結果?因為沒有考慮到到執行緒安全。以下是 Java 檔案有關 SimpleDateFormat 的描述:

 

>

“日期格式是非同步的。

建議為每個執行緒建立單獨的日期格式化實體。

如果多個執行緒併發訪問某個格式化實體,則必須保證外部呼叫同步性。“

>

提示:使用實體變數時,應該每次檢查這個類是不是執行緒安全。

 

正如檔案中提到的那樣,可以為每個執行緒設定不同實體來解決這個問題。如果要共享實體,該如何實現?

 

1. ThreadLocal

 

可以使用 ThreadLocal 解決。Threadlocal 的 get() 方法會給當前執行緒提供正確的值。

 

import java.text.DateFormat;
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 ((DateFormat) SIMPLE_DATE_FORMAT.get()).parse(target);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String format(Date target) {
        return ((DateFormat) SIMPLE_DATE_FORMAT.get()).format(target);
    }
}

 

譯註:實際執行時需要加上強制型別轉換,否則報告編譯錯誤。

 

2. Java 8 執行緒安全的時間日期 API

 

Java8 引入了新的日期時間 API,SimpleDateFormat 有了更好的替代者。如果繼續堅持使用 SimpleDateFormat 可以配合 ThreadLocal 一起使用。但既然已經有了更好的選擇,還是考慮用新的 API。

 

Java 8 提供了幾個執行緒安全的日期類,Java 檔案中這麼描述:

 

“這個類是具有不可變和執行緒安全的特點。”

 

非常值得學習這些類的用法,包括 DateTimeFormatter、OffsetDateTime、ZonedDateTime、LocalDateTime、LocalDate 和 LocalTime。

 

使用新 API 後的程式碼:

 

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);
    }
}

 

總結

 

Java 8 提供的不可變時間是一種解決多日期類執行緒問題的最佳實踐。不可變類本質上是執行緒安全的,應當盡可能使用。

 

程式設計快樂!

已同步到看一看
贊(0)

分享創造快樂