「Java 並行処理プログラミング」という、スレッドに関する超良書があるのだが、それを読んで学んだ結果をまとめてみた。自分向けのメモみたいなものである。興味があるかたは読んでみるとよい。というか、スレッドについて学ぶのであれば即ゲットしたい本である。
Java並行処理プログラミング ―その「基盤」と「最新API」を究める―
- 作者: Brian Goetz,Joshua Bloch,Doug Lea
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/11/22
- メディア: 単行本
- 購入: 23人 クリック: 407回
- この商品を含むブログ (163件) を見る
1.スレッドセーフとは何か?
◯スレッドセーフの定義
クラスが、複数のスレッドからアクセスされた時、以下の条件を満たしつつ、正しく振舞う場合、そのクラスはスレッドセーフである。
- ランタイムにおける各スレッドのスケジューリングに影響されない
- 処理の実行順に影響されない
- 実行のタイミングに影響されない
- 呼び出し側のコードからの同期化努力が不要
- 呼び出し側からの調停努力が不要
◯『正しく振舞う』の定義
クラスが定義する不変項と事後条件を崩さない
- 不変項(invariant)
オブジェクトが持つステート(状態)に課せられる、保たねばならない、状態の制約のこと。ステートを有効と無効に区別する。ステートが状態を持つ時、不変項の制約に準拠した状態を常に取らなくてはいけない。
一つのステートが複数の変数で構成される場合、不変項も一つの変数ではなく、複数の変数に対して適用される。
- 事後条件(postcondition)
ステートを変更する上で許される状態遷移の規定のこと。操作によってステートの状態が変わる時、事後条件で定めた制約通りに遷移しなくてはならない。無効と判断するステートには遷移させない
例) ステート変数;最大値、最小値はそれぞれ変更可能とする。 この時、不変項を以下のように定める ・ステート変数:最小値は0以上 ・ステート変数:最大値は0以上 ・最小値 < 最大値 ステート変数:最小値変更の事後条件 ・変更した値が負数にならない ・変更した値が最大値を上回らない ステート変数:最大値変更の事後条件 ・変更した値が負数にならない ・変更した値が最小値を下回らない
2.スレッドセーフではないパターン
ステートの更新が複数のアクションにまたがる場合、スレッドセーフは崩されやすい。
不運なタイミングによって不正な結果が生じることを競り合い状態という
競り合い状態でよく上がる例
- read-modify-write:読んで・変更して・書き戻す
- check-then-act:チェックして、その観察結果に基づいてアクションをする
- check-if-absent:チェックして無ければ置き換える
いずれも、チェックし、いざ処理を実行するとなったタイミングでスレッドが切り替わり、他のスレッドがチェックと処理まで行ってしまうと不具合が発生する可能性がある(最初のチェック結果が既に無効となっている可能性があるため)。
3.スレッドセーフにするには?
- 指針1 オブジェクトのステートを無くす
メソッド内のローカル変数のみ使用する場合は常にスレッドセーフとなる。一つのスレッドのみしかアクセスさせない方法をスレッド拘束という。
- 指針2 オブジェクトのステートを変更不可にする
変更不可のステートに対するアクセスは常にスレッドセーフ。Javaの場合はfinalにする。
- 指針3 ロックでガードする
複数のスレッドがアクセスする可変なステート変数は、その変数へのすべてのアクセスが同じロックの元で行われる必要がある(すべてのアクセスとは、変更だけでなく読み込みも含むので注意)。
複数のステートで不変項が構成される場合、それらはすべて同一のロックによってガードされる必要がある。
javaの場合、synchronizedでロックする。