以前ConcurrentHashMapについて調べた時にJavaのスレッドセーフについてちゃんと説明できないと思ったので調べた
【Java】HashMapとConcurrentHashMap - UGA Boxxx
ちょっと古いけどこちらの本の第二章を参考にした
スレッドセーフ
オブジェクトがスレッドセーフでなければならないのは、そのオブジェクトが複数のスレッドからアクセスされるときで、オブジェクトの内容や動作ではなく、オブジェクトの使われ方に関係している
オブジェクトをスレッドセーフにするためには、その可変なステートへのアクセスを複数のスレッド間で同期化する必要がある
それを忘れるとデータの破壊や、望ましくない結果が生じる
あるステート変数に複数のスレッドがアクセスし、どれかのスレッドが変数への書き込みをする可能性があるなら、全てのスレッドに対して、その変数へのアクセスを必ず同期化しなければならない
同期化のためのJavaの主な仕組みはsynchronized
で排他的ロックを提供することだが、volatile変数、明示的ロック、アトミック変数なども同期化にあたる
怖いのは、同期化が必要なのにそれをサボったプログラムが、一見まともに動き、試験にもパスして何年もの動き続けること
それはいつ誤作動をしてもおかしくない時限爆弾のようになる
欠陥プログラムを探したり直すより、最初からスレッドセーフに設計した方がずっと楽
スレッドセーフなクラスを設計するときは、カプセル化、不可変性、不変項の明確な指定などの技法が最良
スレッドセーフって何?
本より
クラスは複数のスレッドからアクセスされたとき、ランタイムにおける各スレッドのスケジューリングや実行順や実行のタイミングがどうであっても、また呼び出し側のコードからの同期化努力やそのほかの調停努力がなくても、正しく振る舞うならスレッドセーフ
クラスがスレッドセーフなら、そのインスタンスに対して逐次的または並行的に行われるどんな操作集合も、そのインスタンスを不正なステートにすることはない
ステートレスなオブジェクトはスレッドの操作が影響与えるステートがそもそもないので常にスレッドセーフ
なので、複数のリクエストにまたがって何かを記憶しようとしたときだけ、スレッドセーフが問題になる
アトミック性
++count
は一つのアクションのように見えるが、「現在地を取り出し」、「それに1を加え」、「新しい値を書き戻す」という3つの独立した操作で、つまりアトミックな操作ではない
こういう処理をリード・モディファイ・ライト操作という
これが不運なタイミングで同時に更新されたときに悲惨な事故が起こる
このようにデータの不正が生ずる事件を、データの整合性問題と言い、この可能性がある状態を競り合い状態という
他にもチェック・ゼン・アクト操作もアトミックではない競り合い状態になりうる
スレッドセーフであるためにはこれら複合アクションをアトミックに実行させる必要があり、その基本的な方法はロックすることになる
Java言語のロック
Javaはアトミック性を強制するために、synchronized
ブロックという仕組みをもっている
synchronized
ブロックを使えばアトミックに実行される
ただ、複合アクションをsynchronized
ブロックで包むだけでは不十分で、その変数がアクセスするあらゆる場所で同期化が必要
かつ、同じロックを使う必要がある
実行性能
当然ロックをしている間は他のスレッドは待機状態になるので、実行性能がが悪くなる可能性があるので注意が必要
ロックを使うときは常にsynchronized
ブロックの中のコードがやることを意識するべき
synchronized
ブロックをどれぐらいの範囲で使用するかの決定には複数の設計目標のトレードオフを必要とすることがあるが、合理的なバランスはほとんどの場合見つけることができるので、必要最小限の短さでちゃんと使うことはプログラマが責任をもってやることである