- 3.1. synchronizedブロック
- 3.2. synchronizedブロックの仕組み
- 3.3. synchronized メソッド
- 3.4. static synchronized メソッド
- 3.5. volatile 変数
3.4. static synchronized メソッド
クラス(static)メソッドもsynchronizedメソッドにすることができます。クラスメソッドはそのクラスのインスタンスが存在していなくても呼び出すことができますが、スレッドはどのオブジェクトのロックを取得するのでしょうか。
あるクラスがプログラムにおいて利用可能であるとき、必ずそのクラスに対応するjava.lang.Classクラスのオブジェクトが存在しています。スレッドは、static synchronized メソッドを実行するとき、そのクラスに対応するClassオブジェクトのロックを取得します。
なお、あるクラスに対応するClassオブジェクトは、ClassクラスのクラスメソッドであるforNameメソッドを用いて取得することができます。
class SomeClass { ............. synchronized static public void someMethod() { ............. } }
このプログラムは、次のプログラムと同じ意味になります。
class SomeClass { ............. static public void someMethod() { synchronized(Class.forName("SomeClass")) { ............. } } }
3.5. volatile 変数
synchronized の説明のところにも書いたように、各スレッドは、共有する変数の内容をスレッド固有の作業領域にコピーして作業を行います。したがって、共有する変数を使用するためには、作業コピーへの読み込みや、共有メモリへの書き込みを行う必要があります。この読み書きのタイミングにはある程度の自由が許されており、連続して同じ変数にアクセスするときには途中で書き戻さなかったり、プログラムで記述した順序とは違う順序で書き戻したりすることが許されています。
次のプログラムのincrementメソッドでは、まずaを加算してからbを加算するように記述しています。しかしあるスレッドでincrementメソッド実行中に別スレッドからprintメソッドを実行した場合、(現実にそのようになることはほとんどありませんが)「a=0,b=1」と表示される可能性があります。
class someClass { private int a = 0, b = 0; public void increment() { a++; b++; } public void print(){ System.out.println("a=" + a + ",b=" + b); }
そのような意図しない動作を防ぐためには、共有される変数をvolatileとして宣言します。volatile変数は、スレッドからアクセスがあるたびに、必ず共有メモリ上の変数の値とスレッドの作業コピー上の値とを一致させます。したがって必ず要求した順序で変数の値が書き換えられます。
volatile private int a = 0, b = 0;
無用のトラブルを防ぐには、複数のスレッドからアクセスされる可能性のあるメンバ変数はvolatileとして宣言しておくのがよいでしょう。ただし、synchronizedブロックでのロックの開放時には、必ず作業コピーの内容は共有メモリに書き戻されますので、synchronizedブロック内からしかアクセスされない変数はvolatile変数にする必要はありません。
(実習課題1)
次の条件を満たすプログラムを作成しなさい。
- Runnableインタフェースを実装させる。
- mainメソッドでは自分自身のクラスを元にスレッドを新しく2つ起動させる。
- 2つのスレッドでは共通のAccountインスタンスを扱う。
- 各スレッドのrunメソッドでは、「1秒以下のランダムな時間待機した後、Accountインスタンスのdeposit(1000)とshowBalance()を呼び出す処理」を10回繰り返す。
銀行口座を表すAccountクラスは、次のものを利用すること。プログラムを動作させ、最終的な残高が20000円にならない場合があることを確認すること。
public class Account { private int balance = 0; public void deposit (int money) { int total = balance + money; try { Thread.sleep((long)(Math.random()*1000)); } catch (InterruptedException e) {} balance = total; } public void showBalance() { System.out.println("現在の残高は " + balance + " 円です。"); } }
(実習課題2)
実習課題1のAccountクラスを修正し、最終的な残高が正しく20000円になるようにしなさい。