1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

スレッドセーフでないSimpleDateFormatの動き

Posted at

はじめに

※別のブログに投稿していた記事を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));
		}
	}
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?