static変数の同期化しての利用

SimpleDateFormat をメソッド内で毎回生成するのと static 変数として同期化して利用するのはどちらが早いのか。

http://d.hatena.ne.jp/r_ikeda/20090529/threadlocal

というエントリーがあったので自分も試してみました。
まず、r_ikedaさんの結果と考察です(ソースコードはリンク先で参照のこと)。

[LocalFormat] 2891
[StaticFormat] 1847
[SyncFormat] 1278
[ThreadLocalFormat] 1206

http://d.hatena.ne.jp/r_ikeda/20090529/threadlocal

コード見る限り速度差はなさそうだけど・・・と思い自分も実行。

run:
[LocalFormat] 3594
[StaticFormat] 3484
[SyncFormat] 3563
[ThreadLocalFormat] 3531
BUILD SUCCESSFUL (total time: 14 seconds)

おそっww
自分の環境ではほぼ同じ数値、誤差レベルでばらつきがあるって所です。

やってみたらメソッド内でオブジェクトを生成するよりも同期化して使うほうが早かった。
同じ static 変数を使うやり方でも同期化しないより、した方が早かった。何でだろう。

http://d.hatena.ne.jp/r_ikeda/20090529/threadlocal

一般的には同期化はコストがかかるので、StaticFormatとSyncFormatではSyncFormatの方が遅いはずですね。ほとんど差がでなかった理由はスレッドがmainスレッドしか起動していない状態で、同期処理がほとんど行われていなかったからではないでしょうか?また、メソッド内でオブジェクトを1回だけ生成して回すのではあまり差はでないと思われます。

ちなみに自分の場合は、同期化を意識して書くことはほとんどやりません。同期化に潜んだバグって分かりずらく、同期化処理がボトルネックになる可能性もあるので不要な同期化はしないがベストプラクティスと思っています。

どうしても同期化しなければならない場合

そんな場合は、同期化して処理する専用のクラスを用意してしまいます。そうすれば無学な人がいじる場合でも安全性が高くなります。

public class SyncDateFormater {
    private static final SimpleDateFormat DATE_FORMAT
         = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    public static final String format(Date date) {
        synchronized (DATE_FORMAT) {
            return DATE_FORMAT.format(date);
        }
    }
}

メソッド自体をsynchronized にしない理由は、同期化のスコープを最小限にするためで、クラスメソッドを多数定義しても大丈夫なようにです。また、専用のクラスを作成することで同期処理が一箇所に集中し保守しやすくなります。
尚、こんなことしたら待ちが発生してムダじゃないか!と思ったならば、同期化の意味を考えるといいでしょう。

あまり考えられませんが、どうもここがボトルネックだ、何百という処理がこのメソッドの同期待ちを起こしているというならば、こんな感じに修正しましょう。

public class SyncDateFormater {
    private static final int CAPACITY = 10;
    private static final ArrayBlockingQueue<UnsyncFormatter> FOMATTERS
                   = new ArrayBlockingQueue<UnsyncFormatter>(CAPACITY);


    static {
        for (int i = 0; i < CAPACITY; i++) {
            FOMATTERS.offer(new UnsyncFormatter());
        }
    }

    public static final String format(Date date) {
        UnsyncFormatter formatter = null;
        try {
            formatter = FOMATTERS.take();
            return formatter.format(date);
        } catch (InterruptedException ex) {
            // TODO 例外処理はポリシーに従って
            return new UnsyncFormatter().format(date);
        } finally {
            if (formatter != null) {
                FOMATTERS.offer(formatter);
            }
        }
    }

    private static class UnsyncFormatter {

        private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

        final String format(Date date) {
            return formatter.format(date);
        }
    }
}

BlockingQueueを使って待ち行列を作成しています。最大で10個までの非同期なDateFormatterをキューに溜めておき、1個づつ取り出し(take)して使用しています。takeはキューに存在しない場合は追加されるまでblokingしてくれますので10個までならば処理がここで止まることはありません。処理が終わったならば非同期なDateFormatterはキューに戻してあげます。簡単なプーリングを行っているわけですね。