SimpleDateFormatのjavadocには、SimpleDateFormatが同期されていないことが記載されています。
「日付形式は同期されません。スレッドごとに個別の形式インスタンスを作成することをお勧めします。複数のスレッドが同時に形式にアクセスする場合は、外部で同期する必要があります。」
しかし、マルチスレッド環境でSimpleDateFormatのインスタンスを使用する最良の方法は何ですか。ここに私が考えたいくつかのオプションがあります。過去にオプション1と2を使用しましたが、より良い代替物があるかどうか、またはこれらのオプションのどれが最高のパフォーマンスと並行性を提供するかを知りたいです。
オプション1:必要に応じてローカルインスタンスを作成する
public String formatDate(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
}
オプション2:SimpleDateFormatのインスタンスをクラス変数として作成しますが、アクセスを同期します。
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
synchronized(sdf) {
return sdf.format(d);
}
}
オプション3:ThreadLocalを作成して、スレッドごとにSimpleDateFormatの異なるインスタンスを保存します。
private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
SimpleDateFormat sdf = tl.get();
if(sdf == null) {
sdf = new SimpleDateFormat("yyyy-MM-hh");
tl.set(sdf);
}
return sdf.format(d);
}
SimpleDateFormatの作成は 高価 です。めったに行われない限り、これを使用しないでください。
少しブロックしてもらえればOK。 formatDate()があまり使用されない場合に使用します。
スレッドを再利用する場合の最速オプション( thread pool )。 2より多くのメモリを使用し、起動時のオーバーヘッドが高くなります。
アプリケーションでは、2。と3.の両方が実行可能なオプションです。どのケースが最適かは、ユースケースによって異なります。時期尚早の最適化に注意してください。これが問題だと思われる場合にのみ行ってください。
サードパーティが使用するライブラリには、オプション3を使用します。
もう1つのオプションはCommons LangFastDateFormatですが、日付のフォーマットにのみ使用でき、解析はできません。
Jodaとは異なり、フォーマットのドロップイン置換として機能できます。 (更新:v3.3.2以降、FastDateFormatは FastDateParser を生成できます。これはSimpleDateFormatのドロップインスレッドセーフな置換です)
Java 8を使用している場合、 Java.time.format.DateTimeFormatter
を使用できます。 =:
このクラスは不変であり、スレッドセーフです。
例えば。:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new Java.util.Date().toInstant()
.atZone(ZoneId.systemDefault())
.format(formatter);
Commons Lang 3.xにはFastDateFormatとFastDateParserが追加されました。 SimpleDateFormatよりもスレッドセーフで高速です。また、SimpleDateFormatと同じフォーマット/解析パターン仕様を使用します。
SimpleDateFormatを使用せず、代わりにjoda-timeのDateTimeFormatterを使用してください。構文解析側では少し厳密であるため、SimpleDateFormatに取って代わるものではありませんが、joda-timeは安全性とパフォーマンスの点ではるかに優れています。
Parse()とformat()へのアクセスを同期し、ドロップインの置換として使用できるSimpleDateFormatの単純なラッパークラスを作成します。オプション#2よりも確実に、オプション#3よりも扱いにくい。
SimpleDateFormatを非同期にすることは、Java APIデザイナーの側での設計上の決定が不十分だったようです。
これをオプション3で実装しましたが、いくつかのコードを変更しました。
デフォルト設定が本当に必要な場合を除き、ロケールとタイムゾーンを設定できます(Javaではデフォルトは非常にエラーが発生しやすいです)
private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
return sdf;
}
};
public String formatDate(Date d) {
return tl.get().format(d);
}
別のオプションは、スレッドセーフなキューにインスタンスを保持することです:
import Java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
SimpleDateFormat fmt = dateFormatQueue.poll();
if (fmt == null) {
fmt = new SimpleDateFormat(DATE_PATTERN);
}
String text = fmt.format(date);
dateFormatQueue.offer(fmt);
return text;
}
public Date parse(String text) throws ParseException {
SimpleDateFormat fmt = dateFormatQueue.poll();
if (fmt == null) {
fmt = new SimpleDateFormat(DATE_PATTERN);
}
Date date = null;
try {
date = fmt.parse(text);
} finally {
dateFormatQueue.offer(fmt);
}
return date;
}
DateFormatQueueのサイズは、この関数を同時に定期的に呼び出すことができるスレッドの推定数に近い値にする必要があります。この数よりも多くのスレッドが実際にすべてのインスタンスを同時に使用する最悪の場合、SimpleDateFormatインスタンスが作成され、dateFormatQueueがいっぱいであるため返すことができません。これはエラーを生成せず、一度だけ使用されるSimpleDateFormatを作成するというペナルティを被るだけです。
アプリケーションに1つのスレッドがあるとします。なぜSimpleDateFormat変数へのアクセスを同期するのですか?