はじめに
※別のブログに投稿していた記事をQiitaに投稿し直しています。(元の記事は削除予定)
SimpleDateFormatはAPI仕様にこのような記載がある。
日付フォーマットは同期化されません。
スレッドごとに別のフォーマット・インスタンスを作成することをお薦めします。
複数のスレッドがフォーマットに並行してアクセスする場合は、外部的に同期化する必要があります。
スレッドセーフでないことは分かるが、どのような動きになるか分からないので試してみる。
結論から
API仕様に記載されている通りスレッドごとにインスタンスを作成するか、
Java 8 以降であればDateTimeFormatterを使用する。
スレッドセーフでないバージョン
staticなフォーマッタ
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
複数のスレッドを起動する
Consumer<Date> c = d -> notThreadSafe(d);
ExecutorService e = Executors.newCachedThreadPool();
e.execute(() -> {c.accept(createDate(2019, 1, 1));});
e.execute(() -> {c.accept(createDate(2020, 2, 2));});
e.shutdown();
スレッド処理の中身
private static void notThreadSafe(Date d) {
for (int i = 0; i < 10000; i++) {
System.out.println(sdf.format(d));
}
}
実行結果(ソートして重複除去)
2019-01-01
2019-01-02
2019-02-01
2019-02-02
2020-01-01
2020-02-01
2020-02-02
本番環境で発生すると、迷宮入りしそうな現象・・・orz
スレッドセーフなバージョン
staticなフォーマッタ
private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
複数のスレッドを起動する
Consumer<Date> c = d -> threadSafe(d);
ExecutorService e = Executors.newCachedThreadPool();
e.execute(() -> {c.accept(createDate(2019, 1, 1));});
e.execute(() -> {c.accept(createDate(2020, 2, 2));});
e.shutdown();
スレッド処理の中身
private static void threadSafe(Date d) {
for (int i = 0; i < 10000; i++) {
LocalDate ld = LocalDate.ofInstant(d.toInstant(), ZoneId.systemDefault());
System.out.println(ld.format(dtf));
}
}
実行結果
2019-01-01
2020-02-02
ソースコードの全文
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public class FormatterTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static void main(String[] args) {
// Consumer<Date> c = d -> notThreadSafe(d); // スレッドセーフでないバージョン
// Consumer<Date> c = d -> threadSafe(d); // スレッドセーフなバージョン
ExecutorService e = Executors.newCachedThreadPool();
e.execute(() -> {c.accept(createDate(2019, 1, 1));});
e.execute(() -> {c.accept(createDate(2020, 2, 2));});
e.shutdown();
}
private static Date createDate(int year, int month, int dayOfMonth) {
LocalDate ld = LocalDate.of(year, month, dayOfMonth);
ZonedDateTime zdt = ZonedDateTime.of(ld.atTime(LocalTime.MIN), ZoneId.systemDefault());
Date d = Date.from(zdt.toInstant());
return d;
}
private static void notThreadSafe(Date d) {
for (int i = 0; i < 10000; i++) {
System.out.println(sdf.format(d));
}
}
private static void threadSafe(Date d) {
for (int i = 0; i < 10000; i++) {
LocalDate ld = LocalDate.ofInstant(d.toInstant(), ZoneId.systemDefault());
System.out.println(ld.format(dtf));
}
}
}