Java DateFormatがスレッドセーフではないことについて誰もが注意を払っています。理論的にはこの概念を理解しています。
しかし、これにより私たちが実際に直面する問題を視覚化することはできません。たとえば、クラスにDateFormatフィールドがあり、マルチスレッド環境のクラスのさまざまなメソッド(日付の書式設定)で同じフィールドが使用されているとします。
これが原因ですか:
また、その理由を説明してください。
試してみましょう。
これは、複数のスレッドが共有SimpleDateFormat
を使用するプログラムです。
プログラム:
public static void main(String[] args) throws Exception {
final DateFormat format = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>(){
public Date call() throws Exception {
return format.parse("20101022");
}
};
//pool with 5 threads
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<Date>> results = new ArrayList<Future<Date>>();
//perform 10 date conversions
for(int i = 0 ; i < 10 ; i++){
results.add(exec.submit(task));
}
exec.shutdown();
//look at the results
for(Future<Date> result : results){
System.out.println(result.get());
}
}
これを数回実行すると、以下が表示されます。
例外:
以下に例を示します。
1。
Caused by: Java.lang.NumberFormatException: For input string: ""
at Java.lang.NumberFormatException.forInputString(NumberFormatException.Java:48)
at Java.lang.Long.parseLong(Long.Java:431)
at Java.lang.Long.parseLong(Long.Java:468)
at Java.text.DigitList.getLong(DigitList.Java:177)
at Java.text.DecimalFormat.parse(DecimalFormat.Java:1298)
at Java.text.SimpleDateFormat.subParse(SimpleDateFormat.Java:1589)
2。
Caused by: Java.lang.NumberFormatException: For input string: ".10201E.102014E4"
at Sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.Java:1224)
at Java.lang.Double.parseDouble(Double.Java:510)
at Java.text.DigitList.getDouble(DigitList.Java:151)
at Java.text.DecimalFormat.parse(DecimalFormat.Java:1303)
at Java.text.SimpleDateFormat.subParse(SimpleDateFormat.Java:1589)
3。
Caused by: Java.lang.NumberFormatException: multiple points
at Sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.Java:1084)
at Java.lang.Double.parseDouble(Double.Java:510)
at Java.text.DigitList.getDouble(DigitList.Java:151)
at Java.text.DecimalFormat.parse(DecimalFormat.Java:1303)
at Java.text.SimpleDateFormat.subParse(SimpleDateFormat.Java:1936)
at Java.text.SimpleDateFormat.parse(SimpleDateFormat.Java:1312)
誤った結果:
Sat Oct 22 00:00:00 BST 2011
Thu Jan 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Thu Oct 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
正しい結果:
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
マルチスレッド環境でDateFormatsを安全に使用する別のアプローチは、ThreadLocal
変数を使用してDateFormat
オブジェクトを保持することです。これは、各スレッドが独自のコピーを持ち、必要がないことを意味します他のスレッドが解放するのを待ちます。こうやって:
public class DateFormatTest {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public Date convert(String source) throws ParseException{
Date d = df.get().parse(source);
return d;
}
}
ここに良い post があります。
データの破損が予想されます-例2つの日付を同時に解析している場合、1つの呼び出しが別の呼び出しから汚染される可能性があります。
これがどのように発生するかは簡単に想像できます。解析には、これまでに読んだ内容に関する一定量の状態の維持が含まれることがよくあります。 2つのスレッドが両方とも同じ状態で踏みつけている場合、問題が発生します。たとえば、DateFormat
はcalendar
型のCalendar
フィールドを公開し、SimpleDateFormat
のコードを見ると、いくつかのメソッドがcalendar.set(...)
を呼び出します。その他はcalendar.get(...)
を呼び出します。これは明らかにスレッドセーフではありません。
私はexactDateFormat
がスレッドセーフではない理由の詳細を調べていませんが、私にとってはそれで十分ですitis同期なしでは安全ではありません-安全でない方法の正確な方法はリリース間で変わる可能性があります。
個人的には、代わりに Joda Time のパーサーを使用します。これは、areスレッドセーフであるためです。で始まるAPIと時間:)
Java 8を使用している場合は、 DateTimeFormatter
を使用できます。
パターンから作成されたフォーマッタは、必要に応じて何度でも使用でき、不変であり、スレッドセーフです。
コード:
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String text = date.format(formatter);
System.out.println(text);
出力:
2017-04-17
大まかに言うと、DateFormat
を、多くのスレッドがアクセスするオブジェクトのインスタンス変数として、またはstatic
として定義しないでください。
日付形式は同期されません。スレッドごとに個別の形式インスタンスを作成することをお勧めします。
したがって、Foo.handleBar(..)
が次の代わりに複数のスレッドによってアクセスされる場合:
public class Foo {
private DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
public void handleBar(Bar bar) {
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
あなたが使用する必要があります:
public class Foo {
public void handleBar(Bar bar) {
DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
また、すべての場合において、static
DateFormat
を持たないでください
Jon Skeetが述べたように、外部同期を実行する場合(つまり、synchronized
への呼び出しの周りでDateFormat
を使用する場合)に、静的インスタンス変数と共有インスタンス変数の両方を使用できます。
日付形式は同期されません。スレッドごとに個別の形式インスタンスを作成することをお勧めします。複数のスレッドがフォーマットに同時にアクセスする場合、外部で同期する必要があります。
これは、DateFormatのオブジェクトがあり、2つの異なるスレッドから同じオブジェクトにアクセスしており、そのオブジェクトに対して両方のスレッドが同じメソッドを同時に開始するようにformatメソッドを呼び出して、視覚化できることを意味します適切な結果にならない
何らかの方法でDateFormatを操作する必要がある場合は、何かを行う必要があります
public synchronized myFormat(){
// call here actual format method
}
データが破損しています。昨日、マルチスレッドプログラムで静的DateFormat
オブジェクトがあり、JDBC経由で読み取られる値に対してformat()
を呼び出していることに気付きました。同じ日付を異なる名前(SELECT date_from, date_from AS date_from1 ...
)。このようなステートメントは、WHERE
クラスのさまざまな日付の5つのスレッドで使用されていました。日付は「正常」に見えましたが、値は異なりましたが、すべての日付は同じ年のもので、月と日のみが変更されました。
他の回答は、このような破損を回避する方法を示しています。 DateFormat
を静的ではなく、SQLステートメントを呼び出すクラスのメンバーになりました。同期を使用して静的バージョンもテストしました。どちらもパフォーマンスに違いはなく、うまく機能しました。
最良の回答では、dogbaneはparse
関数の使用例と、それがもたらすものを示しました。以下は、format
関数を確認できるコードです。
エグゼキューター(同時スレッド)の数を変更すると、異なる結果が得られることに注意してください。私の実験から:
newFixedThreadPool
を5に設定したままにすると、ループは毎回失敗します。あなたのプロセッサに応じてYMMVを推測しています。
format
関数は、別のスレッドからの時間をフォーマットすることにより失敗します。これは、format
関数が内部でcalendar
関数の開始時に設定されるformat
オブジェクトを使用しているためです。そして、calendar
オブジェクトはSimpleDateFormat
クラスのプロパティです。はぁ...
/**
* Test SimpleDateFormat.format (non) thread-safety.
*
* @throws Exception
*/
private static void testFormatterSafety() throws Exception {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56);
final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56);
String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"};
Callable<String> task1 = new Callable<String>() {
@Override
public String call() throws Exception {
return "0#" + format.format(calendar1.getTime());
}
};
Callable<String> task2 = new Callable<String>() {
@Override
public String call() throws Exception {
return "1#" + format.format(calendar2.getTime());
}
};
//pool with X threads
// note that using more then CPU-threads will not give you a performance boost
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<String>> results = new ArrayList<>();
//perform some date conversions
for (int i = 0; i < 1000; i++) {
results.add(exec.submit(task1));
results.add(exec.submit(task2));
}
exec.shutdown();
//look at the results
for (Future<String> result : results) {
String answer = result.get();
String[] split = answer.split("#");
Integer calendarNo = Integer.parseInt(split[0]);
String formatted = split[1];
if (!expected[calendarNo].equals(formatted)) {
System.out.println("formatted: " + formatted);
System.out.println("expected: " + expected[calendarNo]);
System.out.println("answer: " + answer);
throw new Exception("formatted != expected");
/**
} else {
System.out.println("OK answer: " + answer);
/**/
}
}
System.out.println("OK: Loop finished");
}
単一のDateFormatインスタンスを操作/アクセスする複数のスレッドがあり、同期が使用されていない場合、スクランブルされた結果を取得する可能性があります。これは、複数の非アトミック操作が状態を変更したり、メモリを一貫性なく表示したりする可能性があるためです。
これは、DateFormatがスレッドセーフではないことを示す私の簡単なコードです。
import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;
import Java.util.Locale;
public class DateTimeChecker {
static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
runThread(target1);
runThread(target2);
runThread(target3);
}
public static void runThread(String target){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
すべてのスレッドが同じSimpleDateFormatオブジェクトを使用しているため、次の例外がスローされます。
Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" Java.lang.NumberFormatException: multiple points
at Sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at Sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at Java.lang.Double.parseDouble(Unknown Source)
at Java.text.DigitList.getDouble(Unknown Source)
at Java.text.DecimalFormat.parse(Unknown Source)
at Java.text.SimpleDateFormat.subParse(Unknown Source)
at Java.text.SimpleDateFormat.parse(Unknown Source)
at Java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.Java:24)
at Java.lang.Thread.run(Unknown Source)
Java.lang.NumberFormatException: multiple points
at Sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at Sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at Java.lang.Double.parseDouble(Unknown Source)
at Java.text.DigitList.getDouble(Unknown Source)
at Java.text.DecimalFormat.parse(Unknown Source)
at Java.text.SimpleDateFormat.subParse(Unknown Source)
at Java.text.SimpleDateFormat.parse(Unknown Source)
at Java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.Java:24)
at Java.lang.Thread.run(Unknown Source)
Java.lang.NumberFormatException: multiple points
at Sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at Sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at Java.lang.Double.parseDouble(Unknown Source)
at Java.text.DigitList.getDouble(Unknown Source)
at Java.text.DecimalFormat.parse(Unknown Source)
at Java.text.SimpleDateFormat.subParse(Unknown Source)
at Java.text.SimpleDateFormat.parse(Unknown Source)
at Java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.Java:24)
at Java.lang.Thread.run(Unknown Source)
しかし、異なるオブジェクトを異なるスレッドに渡すと、コードはエラーなしで実行されます。
import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;
import Java.util.Locale;
public class DateTimeChecker {
static DateFormat df;
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target1, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target2, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target3, df);
}
public static void runThread(String target, DateFormat df){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
これらは結果です。
Thread-0 Thu Sep 28 17:29:30 IST 2000
Thread-2 Sat Sep 28 17:29:30 IST 2002
Thread-1 Fri Sep 28 17:29:30 IST 2001