- 3.1. synchronizedブロック
- 3.2. synchronizedブロックの仕組み
- 3.3. synchronized メソッド
- 3.4. static synchronized メソッド
- 3.5. volatile 変数
3.2. synchronizedブロックの仕組み
クラスやインスタンスはそれぞれ「ロック」というものを持っています。ロックとは「鍵」のことです。スレッドは、synchronizedブロックの開始時にsynchronized文で指定したオブジェクトのロックを取得し、終了時にそのロックを開放します。あるスレッドがロックを取得している状態では、他のスレッドは同じロックを取得することができません。ロックが返却されるまで待たされます。
つまり、同じオブジェクトのロックを獲得しようとするsynchronizedブロック同士は、原則として同時には並行して実行されないことになります。オブジェクトに鍵をかけて、他のスレッドに邪魔されないようにして作業をしていると考えるとよいでしょう。
ただし、オブジェクトに対して鍵をかけると考えると、synchronizedブロックの実行は絶対に割り込まれないと思うかもしれませんが、それは誤りです。ロックが使用中であるかどうかを気にするのは、あくまでも、スレッドがsynchronizedブロックを実行しようとしているときだけです。あるスレッドがsynchronizedブロックでオブジェクトを独占しているつもりでも、synchronizedを利用していない箇所からのアクセスを防ぐことはできません。
Accountクラスに、5%の利息を加算するaddInterestメソッドを追加するとしましょう。
public void addInterest(){ balance = (int)(balance * 1.05); }
depositメソッドでの処理をsynchronizedブロックで実行していたとしていても、dipositメソッド実行中にaddInterestメソッドが同時に実行される可能性は残されています。addInterestメソッドを実行するスレッドは、実行時にロックが使用中であることを確認しないからです。意図した通りの動作をさせたい場合、このaddInterestメソッドでもsynchronizedブロックを使用しなければいけません。
3.3. synchronized メソッド
メソッド全体をオブジェクトthisに対するsynchronizedブロックとしたい場合、synchronizedメソッドを利用することができます。synchronizedメソッドは、メソッドの処理全体を、thisに対するsynchronizedブロックで囲んだのと同じ意味を持ちます。
したがって、depositメソッドは次のように書き換えることができます。
synchronized public void deposit(int money){ int total = balance + money; balance = total; }
この例のような単純な処理では、synchronizedメソッドを使用することは問題ありません。しかし、より長いメソッドや処理に時間のかかるメソッドはむやみにsynchronizedメソッドにすべきではありません。他のスレッドがロック解放待ちのために長時間待たされるおそれがあります。排他制御は、誤動作が起きない必要最低限の狭い範囲に限定して使用するべきです。