IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Effective Java 読書会 12 日目 「スレッド・セーフってなによ!!」

スレッドセーフ、スレッドセーフって何なのよ!本当の気持ち聞かせてよ!

/**   お前への愛は、... - 消しゴム - はてなセリフ

はじめに

皆様おひさしぶりです。
久しぶりに Effective Java 読書会のまとめを上げていきたいと思います!!!

今回の範囲

269 ページ 〜 278 ページ

synchronized を付ければスレッド・セーフってわけではない

synchronized はスレッド・セーフを実現するための一つの方法(実装の詳細)であって、

  • synchronized を使っているからスレッド・セーフというわけではないし、
  • synchronized を使っていないからスレッド・セーフじゃないというわけではない

そもそも、スレッド・セーフとは何か

  • そのクラスのインスタンスに対するどんな順番で行われる操作も仕様的に正しく振る舞い続ける
  • 複数のスレッドから、どんな順番で操作が行われても同様に正しく振る舞い続ける

みたいなこと
そもそも「正しく振る舞い続ける」とは何か?というのは、そのクラスの設計者が決めることなので
「正しい振る舞い」とは何なのかという仕様がないと「スレッド・セーフ」は語れないよね

つまり、「このコードはスレッドセーフだ!!」というには

文書化が必要

スレッド・セーフのレベル

  • 不変 (immutable)
    • 不変なので状態を持たない
  • 無条件スレッドセーフ (unconditionally thread-safe)
    • すべてのメソッドにおいて、外部同期の必要はない。 Random とか
  • 条件付きスレッドセーフ (conditionally thread-safe)
    • メソッドのいくつかが外部同期を必要。Collections の synchronized ラッパー
  • スレッドセーフでない (not thread-safe)
    • まったく同期などはしていない。 ArrayList や HashMap など
  • スレッド敵対 (thread-hostile)
    • マルチスレッド環境で使うな System.runFinalizersOnExit

遅延初期化を注意して使用する

基本的には、遅延初期化しなくてもいい。
初期化チェーンを切りたいときとかに使いましょう。

普通の初期化
private final FieldType field = computeFieldValue();
FieldType getField() {
    return field;
}
同期化されたメソッドで遅延初期化
private FieldType field;

// 毎回、同期化されたコードを実行することになる。
// (パフォーマンスが気になる場合は、二重チェックイディオムを使う)
synchronized FieldType getField() {
    if (field == null) {
        field = computeFieldValue();
    }
    return field;
}
二重チェックイデオム

前の例より、複雑だけど速い

private volatile FieldType field;

FieldType getField() {
    FieldType result = field; // フィールドのスナップショットを取る(volatile によって可視性が保証される)
    if (result == null) { // 同期ブロックのの外からチェック(一度初期化されるとロックされることなく、値が返る)
        synchronized (this) { // ここで同期
            result = field; // もう一回チェック(複数のスレッドがここに入ってくる可能性があるため)
            if (result == null) {
                field = result = computeFieldValue(); //初期化
            }
        }
    }
    return result;
}
単一チェックイデオム

初期化が何回も走っても良い場合

private volatile FieldType field;

FieldType getField() {
    FieldType result = field; // フィールドのスナップショットを取る(volatile によって可視性が保証される)
    if (result == null) {
        field = result = computeFieldValue(); // 初期化、何回か実行される可能性はある
    }
    return result;
}

スレッドスケジューラに依存したコードを書かない

一時的なパフォーマンス改善などにはなるかもしれないが、移植可能じゃなくなる><
そもそも、スレッドが有益なことをしていない状況になる場合は、スレッドにすることがダメ><

ThreadGroup

使っちゃダメよ><

まとめ

マルチスレッドに関する仕様を javadoc に書こう!