ジョブ、スケジュール・ルール を使用すると、他のジョブとの関連でいつジョブを実行するかを制御できます。
特に、並行性によって不整合が生じるような場合に、スケジュール・ルールを使用して、複数のジョブが並行して実行されないようにすることができます。
また、一連のジョブの実行順序を保証することもできます。
スケジュール・ルールの利点については、例で確認すると最も良くわかります。
まず、ライト・スイッチのオン/オフを並行して切り替える場合に使用される 2 つのジョブを定義します。
public class LightSwitch {
private boolean isOn = false;
public boolean isOn() {
return isOn;
}
public void on() {
new LightOn().schedule();
}
public void off() {
new LightOff().schedule();
}
class LightOn extends Job {
public LightOn() {
super("Turning on the light");
}
public IStatus run(IProgressMonitor monitor) {
System.out.println("Turning the light on");
isOn = true;
return Status.OK_STATUS;
}
}
class LightOff extends Job {
public LightOff() {
super("Turning off the light");
}
public IStatus run(IProgressMonitor monitor) {
System.out.println("Turning the light off");
isOn = false;
return Status.OK_STATUS;
}
}
}
次に、ライト・スイッチを作成し、再度オン/オフを切り替える簡単なプログラムを作成します。
LightSwitch light = new LightSwitch();
light.on();
light.off();
System.out.println("The light is on? " + switch.isOn());
この簡単なプログラムを十分な回数実行すると、最終的に以下の出力を取得します。
Turning the light off
Turning the light on
The light is on? true
このような出力の原因は何でしょうか。
ライトをオンにしてからオフにするように指示したため、終了的な状態はオフになる必要があります。
問題は、LightOff ジョブが LightOn ジョブと同時に実行されることを妨げるものが何もないことです。
「オン」ジョブが最初にスケジュールされていてもこれらのジョブが並行して実行されるのは、2 つの
並行ジョブの正確な実行順序を予測する手段がないためです。
LightOn ジョブの前に LightOff ジョブの実行が終了すると、
このような無効な結果を得ることになります。
2 つのジョブが並行して実行されないようにする必要があります。このような場合に、スケジュール・ルールが役立ちます。
mutex (バイナリー・セマフォー とも呼ばれる) として機能する簡単なスケジュール・ルールを作成して、今回の例を修正できます。
class Mutex implements ISchedulingRule {
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
}
次に、このルールを前の例の 2 つのライト・スイッチ・ジョブに追加します。
public class LightSwitch {
final MutextRule rule = new MutexRule();
...
class LightOn extends Job {
public LightOn() {
super("Turning on the light");
setRule(rule);
}
...
}
class LightOff extends Job {
public LightOff() {
super("Turning off the light");
setRule(rule);
}
...
}
}
2 つのライト・スイッチ・ジョブがスケジュールされた場合、
ジョブ・インフラストラクチャーによって isConflicting メソッドが呼び出され、2 つの
ジョブのスケジュール・ルールが比較されます。
これにより、2 つジョブに競合するスケジュール・ルールがあることが認識され、
これらのジョブが正しい順序で実行されるようになります。
また、これらのジョブが同時に実行されないようになります。
これで例のプログラムを百万回実行したとしても、常に同じ結果になります。
Turning the light on
Turning the light off
The light is on? false
ルールは、一般的なロック・メカニズムとしてジョブから独立して使用することもできます。
以下の例では、try/finally ブロック内のルールを獲得し、beginRule と endRule の呼び出しの
間はこのルールで他のスレッドやジョブが実行されないようにしています。
IJobManager manager = Platform.getJobManager();
try {
manager.beginRule(rule, monitor);
... do some work ...
} finally {
manager.endRule(rule);
}
このようなコーディング・パターンを使用してスケジュール・ルールを獲得および解放する場合は、
特に注意する必要があります。
beginRule を呼び出したルールの終了に失敗した場合、このルールはロックされたままになります。
ジョブ API によってスケジュール・ルールの契約が定義されている場合でも、 スケジュール・ルールの実装が実際に提供されるわけではありません。 基本的に、汎用インフラストラクチャーには、どのジョブ・セットが並行して実行できるかどうかを 判別する手段がありません。デフォルトでジョブにスケジュール・ルールはなく、 スケジュールされたジョブは、ジョブを実行するスレッドが作成されるとすぐに実行されます。
ジョブにスケジュール・ルールがある場合は、isConflicting メソッド を使用して、このルールが、並行して実行されているジョブのルールと競合しているか どうかが判別されます。 このため、isConflicting の実装によって、ジョブを安全に実行することができる 正確なタイミングを定義できます。 上記のライト・スイッチの例では、isConflicting 実装は、単に、提供されたルールとの ID 比較を使用しています。 別のジョブに同一のルールがある場合は、これらのジョブは並行して実行されません。 独自のスケジュール・ルールを作成する場合、isConflicting の API 契約を読み、慎重に従うようにしてください。
関連のないいくつかの制約がジョブにある場合 、MultiRule を使用して、 複数のスケジュール・ルールを組み合わせることができます。 例えば、ジョブでライト・スイッチをオンにし、ネットワーク・ソケットへの情報の書き込みも必要な場合、 ジョブには、ライト・スイッチのルールとソケットへの書き込みアクセスのルールがあり、これらのルールは、 ファクトリー・メソッド MultiRule.combine を使用して単一のルールに結合されます。
isConflicting メソッドについては ISchedulingRule で 説明しましたが、contains メソッドについてはまだ説明していません。このメソッドは、 多くのクライアントには必要のない可能性があるスケジュール・ルールの かなり特殊なアプリケーションで使用されます。 スケジュール・ルールを論理的に階層形式で構成して、もともと階層形式になっているリソースへのアクセスを制御できます。 この概念を示す最も簡単な例は、ツリー・ベースのファイル・システムです。 アプリケーションがディレクトリーに対する排他ロックを獲得しようとする場合、 通常、このディレクトリー内のファイルおよびサブディレクトリーへの排他的アクセスも獲得しようとします。 contains メソッドを使用して、ロック間の階層関係を指定します。 ロックの階層を作成する必要がない場合は、contains メソッドを 実装して isConflicting を呼び出すことができます。
java.io.File ハンドルへの書き込みアクセスを制御する階層ロックの例を以下に示します。
public class FileLock implements ISchedulingRule {
private String path;
public FileLock(java.io.File file) {
this.path = file.getAbsolutePath();
}
public boolean contains(ISchedulingRule rule) {
if (this == rule)
return true;
if (rule instanceof FileLock)
return path.startsWith(((FileLock) rule).path);
if (rule instanceof MultiRule) {
MultiRule multi = (MultiRule) rule;
ISchedulingRule[] children = multi.getChildren();
for (int i = 0; i < children.length; i++)
if (!contains(children[i]))
return false;
return true;
}
return false;
}
public boolean isConflicting(ISchedulingRule rule) {
if (!(rule instanceof FileLock))
return false;
String otherPath = ((FileLock)rule).path;
return path.startsWith(otherPath) || otherPath.startsWith(path);
}
}
既に 1 つのルールを所有するスレッドが 2 つ目のルールの獲得を実行する場合、contains メソッドを使用します。
デッドロックが発生する可能性を回避するために、ある一定の時間に指定のスレッドが所有できる
スケジュール・ルールの数は 1 つ のみにしてください。
既に 1 つのルールを所有するスレッドが、前の beginRule 呼び出しを介して、または
スケジュール・ルールを持つジョブを実行することによって beginRule を
呼び出した場合、2 つのルールが同等かどうかを確認するために contains メソッドが問い合わせられます。
既に所有されているルールの contains メソッドによって true が
戻された場合、beginRule 呼び出しは成功します。contains メソッドに
よって false が戻された場合は、エラーが発生します。
より具体的に示すと、スレッドがディレクトリー「c:\temp」に対して例の FileLock ルールを 所有しているとします。 このルールを所有している間は、 このディレクトリー・サブツリー内のファイルのみの変更が許可されます。「c:\temp」の下ではない 他のディレクトリーのファイルを変更しようとしても失敗します。 このため、スケジュール・ルールは、どのジョブやスレッドが実行を許可されたか、または許可されていないかについての具体的な仕様となります。 この仕様に違反すると、実行時例外が発生します。並行性に関する話では、 この技法は 2 フェーズ・ロッキング と呼ばれています。2 フェース・ ロッキング・スキームでは、プロセスは、特定のタスクに必要なすべてのロックを事前に指定する必要があり、 操作中は他のロックの獲得を許可されません。 2 フェーズ・ロッキングによって、循環待機デッドロックの前提条件である保留/待機状態が解消されます。 このため、ロッキング・プリミティブとしてスケジュール・ルールを 1 つ だけ使用するシステムでは、デッドロックに入ることができません。