Single Thread Execution のメモ
Single Thread Execution は、以下の場合に使用される。
- マルチスレッドの場合
- SharedResource 役が複数のスレッドからアクセスされる場合
- 状態(メンバ)が変化する可能性があるとき
デッドロックになる可能性があるのは以下の場合。
- 複数の SharedResource 役がある。
- あるスレッドが、ある SharedResource 役のロックを取ったまま、他の SharedResource 役のロックを取りに行く可能性がある。
- SharedResource 役のロックを取る順序が定まっていない。
逆に言えば、上記のどれか一つでも崩せばデッドロックは起こらなくなる。
マルチスレッドでは unsafeMethod(複数のスレッドから同時に呼び出されると危ないメソッド)に気を配る必要がある。
サブクラス化する場合は特に注意。
synchronized(boost なら mutex)を見かけたら「この synchronized は何を守っているのか」ということを考えるといい。
何も守っていないのであれば synchronized にする必要はない。
というかコメントに何を守っているかを書いておいた方がいいかもしれない(大抵の場合は this だろうから必要ないかも)。
次に考えるのは「他の場所でもちゃんと守っているか」ということ。
複数のスレッドから共有されているフィールドにアクセスするメソッドは、全て synchronized で守る必要がある。
複数のスレッドから共有されているフィールドへアクセスするメソッドを全部 synchronized メソッドにすればいいというだけでなく、synchronized にする単位も考える必要がある。
public synchronized void pass(String name, String address) { this.counter++; this.name = name; this.address = address; check(); } // ↑が name と address を synchronized で守って更新しているのに、 // ↓ではそれぞれ単体で更新できるため、それが無意味になっている。 public synchronized void setName(String name) { this.name = name; } public synchronized void setAddress(String address) { this.address = address; }
でもこれを考えるのは難しそう。もっと一般的に考えられる方法があればいいんだけど……。
例えばサンプルでは toString が synchronized になってる。
public synchronized String toString() { return "No." + counter + ": " + name + ", " + address; }
これはパッと見、読み取り専用だから synchronized にする必要が無さそうに見える。
けど counter, name, address は pass メソッド内で書き換えられるので、場合によっては矛盾した結果を返す場合がある。
しかも toString(オブジェクトを文字列化する)なんてのはデバッグ用でよく使うので、synchronized を付け忘れて使用してるとものすごく痛い目を見ることになりそう。
これを防ぐためには出来る限り多くの(適切な)テストと、出来る限り多くの人にコードレビューしてもらうことが重要か。