(給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 提供的不可變時間是一種解決多日期類執行緒問題的最佳實踐。不可變類本質上是執行緒安全的,應當盡可能使用。
程式設計快樂!
朋友會在“發現-看一看”看到你“在看”的內容