SimpleDateFormatのスレッド安全性(スレッドアンセーフ)を確認する


Javaではよく日付の入出力にSimpleDateFormatが利用されると思います。このクラスはスレッドセーフではありません。今日はこれがなぜスレッドセーフではないのかを確かめてみました。

いつものようにサンプルコードを用意します。

import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatControl implements Runnable {

  private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
  private Date date;

  public SimpleDateFormatControl(Date date) {
    this.date = date;
  }

  @Override
  public void run() {
    String str = sdf.format(date);
    System.out.println(str);
  }

}
import java.util.Date;

public class Main {

  public static void main(String[] args) {
    Date date1 = new Date();
    SimpleDateFormatControl r1 = new SimpleDateFormatControl(date1);

    Date date2 = new Date();
    date2.setYear(0);
    SimpleDateFormatControl r2 = new SimpleDateFormatControl(date2);

    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);

    t1.start();
    t2.start();
  }

}

これを実行すると今日の日付と1900年の日付が画面上に表示されます。

SimpleDateFormatはスレッドアンセーフですから、タイミングによっては同じ日付が出てしまいます。これを意図的に再現しようと思います。

EclipseでSimpleDateFormatクラスのformat(Date, StringBuffer, FieldDelegate)メソッドにブレークポイントを仕込んでデバッグ実行します。

// Convert input date to time field list
calendar.setTime(date);

ここでcalendarメンバ変数に値を格納して、後のステップ(subFormatメソッド)でこれを利用しています。2つのスレッドで同時にメソッドに入った場合、calendar変数が後のスレッドで書き換えられてしまうので、2つのスレッドで同じcalendarを使うことになります。この場合、最終的に画面上には同じ値が2つ表示されます。

前に何度かSimpleDateFormatのスレッド安全性を忘れてコーディングしたことがあるので、これからも気を付けたいです。

じゃあどうすればいいのか(解決策)

SimpleDateFormatを使う部分が常にシングルスレッドであることを保証できないのであれば、素直に都度newしましょう。

上記の場合SimpleDateFormatControlクラスにstaticなSimpleDateFormatがありますが、これは辞めましょう。sdf変数をローカル変数にして、メソッドが呼ばれるたびにnewしましょう。そうすればそのnewしたオブジェクトを共有する事は無いので、スレッドセーフな作りになります。

毎回newしてたら遅いのでは?と思うかもしれませんが、それほど遅くないので安心してください。(そこをnewするコストよりも他の部分の方が圧倒的に遅いはずなので、性能に困ったら他の圧倒的に遅いロジックを何とかしましょう)