5

SimpleDateFormatが単純なJUnitテストを通じてスレッドセーフではないことを、同僚に示したいと思います。次のクラスは私の主張を理解できず(マルチスレッド環境でSimpleDateFormatを再利用)、その理由がわかりません。SDFの使用がランタイム例外をスローするのを妨げている原因を見つけることができますか?

public class SimpleDateFormatThreadTest
{
    @Test
    public void test_SimpleDateFormat_MultiThreaded() throws ParseException{
        Date aDate = (new SimpleDateFormat("dd/MM/yyyy").parse("31/12/1999"));
        DataFormatter callable = new DataFormatter(aDate);

        ExecutorService executor = Executors.newFixedThreadPool(1000);
        Collection<DataFormatter> callables = Collections.nCopies(1000, callable);

        try{
            List<Future<String>> futures = executor.invokeAll(callables);
            for (Future f : futures){
                try{
                    assertEquals("31/12/1999", (String) f.get());
                }
                catch (ExecutionException e){
                    e.printStackTrace();
                }
            }
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class DataFormatter implements Callable<String>{
    static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    Date date;

    DataFormatter(Date date){
        this.date = date;
    }

    @Override
    public String call() throws RuntimeException{
        try{
            return sdf.format(date);
        }
        catch (RuntimeException e){
            e.printStackTrace();
            return "EXCEPTION";
        }
    }
}
4

3 に答える 3

13

スレッドセーフの欠如は、必ずしもコードが例外をスローすることを意味するわけではありません。これは、Andy Groveの記事、SimpleDateFormatとThread Safetyで説明されており、オンラインでは利用できなくなりました。その中で、彼はSimpleDateFormat、異なる入力が与えられた場合、出力が常に正しいとは限らないことを示すことによって、スレッドセーフの欠如を示しました。

このコードを実行すると、次の出力が得られます。

    java.lang.RuntimeException: date conversion failed after 3 iterations.
    Expected 14-Feb-2001 but got 01-Dec-2007

「01-Dec-2007」は、テストデータの文字列の1つでもないことに注意してください。これは実際には、他の2つのスレッドによって処理されている日付の組み合わせです。

元の記事はオンラインで利用できなくなりましたが、次のコードは問題を示しています。これは、AndyGroveの最初の記事に基づいているように見える記事に基づいて作成されました。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class SimpleDateFormatThreadSafety {
    private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);

    public static void main(String[] args) {
        new SimpleDateFormatThreadSafety().dateTest(List.of("01-Jan-1999", "14-Feb-2001", "31-Dec-2007"));
    }

    public void dateTest(List<String> testData) {
        testData.stream()
                .map(d -> new Thread(() -> repeatedlyParseAndFormat(d)))
                .forEach(Thread::start);
    }

    private void repeatedlyParseAndFormat(String value) {
        for (int i = 0; i < 1000; i++) {
            Date d = tryParse(value);
            String formatted = dateFormat.format(d);
            if (!value.equals(formatted)) {
                throw new RuntimeException("date conversion failed after " + i
                        + " iterations. Expected " + value + " but got " + formatted);
            }
        }
    }

    private Date tryParse(String value) {
        try {
            return dateFormat.parse(value);
        } catch (ParseException e) {
            throw new RuntimeException("parse failed");
        }
    }
}

間違った日付を返すことでこの変換が失敗することもあれば、NumberFormatException:で失敗することもあります。

java.lang.NumberFormatException: For input string: ".E2.31E2"
于 2012-09-21T15:19:07.960 に答える
4

のjavadocのSimpleDateFormatterこの部分には、十分な証拠がありませんか?

同期

日付形式は同期されません。スレッドごとに個別のフォーマットインスタンスを作成することをお勧めします。複数のスレッドが同時にフォーマットにアクセスする場合は、外部で同期する必要があります。

そして、スレッドセーフではないという主な観察は、例外ではなく、予期しない結果を取得することです。

于 2012-09-21T15:20:11.823 に答える
3

SimpleDateFormat(sun JVM 1.7.0_02の)次のコードのため、スレッドセーフではありません。

private StringBuffer format(Date date, StringBuffer toAppendTo,
                            FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);
    ....
}

formatを呼び出すたびに、日付がのcalendarメンバー変数に格納されSimpleDateFormat、その後、calendar変数のコンテンツ(dateパラメーターではない)にフォーマットが適用されます。

したがって、formatを呼び出すたびに、現在実行中のすべてのフォーマットのデータが(アーキテクチャのコヒーレンスモデルに応じて)変更される可能性がありますcalendar。これは、他のすべてのスレッドで使用されるメンバー変数のデータです。

したがって、フォーマットするために複数の同時呼び出しを実行する場合、例外は発生しない可能性がありますが、各呼び出しフォーマットするための他の呼び出しの1つの日付から派生した結果、またはフォーマットするための多くの異なる呼び出しからのデータのハイブリッドの組み合わせを返す場合があります。

于 2012-09-21T15:27:44.210 に答える