2017.09.20
Monitor
目次
Monitor
Javaは言語レベルでマルチスレッドを実装しました。そのため、スレッド間のデータアクセスや共有のために同期という方法を使用します。この同期を実装するために、Javaはモニター(Monitor)を使用します。
モニターは、以下の図の中で緑色のエリアとなります。特定のデータの利用は、このエリアだけを使用して行います。しかし、このエリアのスペースは、スレッドがたった一つしか入って行くことができないので、いくつかスレッドが利用する場合、使用中スレッドが出るまでは、誰もがエリアを使用することができない。そのため、このエリアを使用しているスレッドは、自分が特定のデータを使用する作業の独立性を確保されるのです。これはまるでOracleから特定のリソースを使用するために取得するロック(lock)やラッチ(latch)と同じだと言えます。
スレッドがモニターで保護されたリソースを利用するためには、モニターを獲得しなければなりません。このモニターを獲得するためには、エントリーの設定が必要となりますが、モニターを獲得しているスレッドがなく、エントリーも設定されていない場合には、モニターをすぐに獲得することができます。しかし、誰かモニターを獲得した状態で作業をしているときは、エントリーを設定して待機しなければなりません。 モニターを所有しているスレッドが作業を終えてモニターを解放すると、エントリーを設定して待機しているスレッドは、互いにモニターを獲得するために競合しなければならなくなり、最終的な勝者がモニターを獲得することになります。 待機設定に関しては後述することとします。
モニターは二種類の同期のために使用され、一つは相互排除(mutual exclusion)、もう一つは協力(cooperation)です。 相互排除(mutual exclusion)は、共有データに対してそれぞれの作業が保障されるようにスレッドの独立性を保障するものであり、協力(cooperation)は同じ目的を持ったスレッドがそれぞれ調和作業をできるようにすることを意味します。ここで、前者はオブジェクトロック(object lock)を利用しており、後者はオブジェクトクラスのwait、notify メソッドを使用します。
相互排除(mutual exclusion)は単純に、特定のモニターを相互排他的に使用することを意味します。一般的に、この相互排除(mutual exclusion)は多数のスレッドがデータを共有する際に、互いに干渉せずに使用するために必須となります。 JVMは、タイムスライスを実装していないので、優先度の高いスレッドの場合は、相対的に優先順位の低いスレッドとの間で何の干渉も受けなくなります。そのため、優先順位の高いスレッドがCPUを独占する事象が発生した場合、これにより、優先順位の低いスレッドは、CPUを獲得できない状態が継続します。この場合、モニターが、すべてのスレッドが均等に機会を持つことができるように調整する役割をすることになります。
協力(cooperation)は、あるスレッドが特定の状態の任意のデータを必要とし、他のスレッドが同じ状態のデータをインポートする必要がある場合に使用されます。例えば、あるスレッドがバッファーからデータを読み取る作業をする場合、この時点でバッファーは「not empty」状態でなければならなりません。もし「empty」状態であれば、このスレッド(読込スレッド)は待機しなければならりません。他のスレッド(書込みスレッド)は、バッファーにデータを書き込む作業を行います。したがって、この書込みスレッドはバッファーをを「not empty」状態にしてくれる役割をすることになります。 書込みスレッドがバッファーにある内容を記録する時、読込スレッドはJVMが‘Wait and Notify’と命名したモニターを利用することができます。
このモニターは、次のように動作します。 モニターを所有しているスレッドがwait commandを実行して、自分自身をsuspend状態にし、スレッドが待機状態になると、モニターを放棄し、wait setに状態になります。このスレッドは、モニターを代わりに使用しているスレッドがnotify commandを実行するまでwait setの状態で待機することにります。 Notifyを実行したスレッドが作業を終了してモニターを放棄するまで継続してモニターを所有することになる。 (そのため、このモニターをSignal and Continue monitorとも言います。)スレッドがモニターを放棄すると、今まで待機していたスレッドは再び動作を開始しモニターを再獲得することになります。
前例を持って説明しますと読込スレッドがモニターを獲得してバッファーの状態をチェックした時、「not empty」状態の場合バッファーからデータを読込作業が完了したら、monitorを放棄します。逆に「empty」状態である場合、モニターを放棄して、wait setに入ります。書込みスレッドはモニターを獲得した後、バッファーにデータを書込みnotifyを実行します。 書込みスレッドがnotifyを実行するときに読込スレッドは作業を再開するためにmakingになります。 書込み処理が終了したら、書込みスレッドはモニターを放棄しバッファーは「not empty」状態になります。読込スレッドは再びモニターを獲得してバッファーの状態をチェックし「not empty」状態であればバッファーの内容を読んで作業をした後monitorを放棄します。ところが、書込みスレッドがモニターを放棄した時、読込スレッド以外のスレッドがモニターを獲得することもできる。そうなれば、バッファーは再び「empty」の状態になることがありますので読込スレッドは再びバッファーの状態をチェックすることになります。
上の図はモニターの動作過程を簡単に表現しています。 あるスレッドがモニターで保護されるデータに接近するために①のようにEntry Setに入ってきます。もし現在モニターを所有しているスレッドがなく、entry setも空いているならばこのスレッドは②のようにモニターをを取得することになります。 このスレッドがモニターを所有している間、他のスレッドが同一データを使用するとするならば、このこのスレッドはentry setで待機しなければならなくなります。この待機スレッドはブロック状態になって、モニターを所有しているスレッドがモニター放棄することで、モニターを取得するまでこの状態を維持することになります。 上の図で見れば3個のthreadがブロック状態で待機中であることを見ることができます。
モニターを取得したスレッドすなわちアクティブスレッド(active thread)がモニターを放棄するには、以下の二種類です。 一つは作業が完了した場合、もう一つはwait commandを実行した場合です。 前者の場合は⑤のようにmonitorを放棄してこの状態から抜け出します。 後者の場合は③のようにwait setに入ることになります。アクティブスレッド(active thread)がモニターを放棄する前にnotifyをしない場合は、entry setにあるスレッドだけがモニターの獲得を競争することになります。 Notify commandを実行する場合は、entry setとwait setにあるすべてのスレッドが競争することになります。 もしwait setのスレッドが競争で勝利すれば④の経路を通りモニターを獲得することになります。 Entry setのスレッドが競争で取得することになれば当然②の経路を通りモニターを獲得することになります。スレッドがwait setにあればwaited状態になっています。スレッドがactive状態の場合にのみwait commandをでき、そうすることでwait setに入ってwaited状態になることができます。 またwait setに進入したスレッドの場合モニターを獲得する方法以外には、wait setから抜け出すことはできません。
スレッドがwait commandを実行するときにタイムアウトを設定することができます。 タイムアウトを設定した場合は、タイムアウトが終わるまでは、どのスレッドももこの待機スレッドにnotifyを実行することができません。 タイムアウト時間になれば、JVMが自動的にnotifyを実行するようになっており、notify commandが実行されなくてもwaiting threadは目覚めることなります。
モニターについて概略的に説明しました。ここで、抜けている部分は、競合状況でスレッドを選択する方法ですが、これはJVMを実装する方にまかされています。これは単にQueueを使用することもPriority別にQueueに実装することもありstack式でLIFO方式を使用することもできます。したがって、これについての詳細は、各JVMベンダーの情報を取得する必要があります。
参考資料
INSIDE THE JAVA VIRTUAL MACHINE, Bill Venners, McGraw-Hill, 2000