ロックにはライフサイクルがあり、さまざまな種類のロックはさまざまな方法で他のロックと互換性を持ちます。ロックはデッドロック・シナリオにならないように、正しい順序で処理する必要があります。
前の定義から、S ロック・モードが U ロック・モードよりも弱体であることは明白です。それは、同一マップ・エントリーにアクセスするときに、より多くのトランザクションが並行して実行されることを許すためです。 U ロック・モードは、S ロック・モードよりも少し強力です。それは、U ロック・モードまたは X ロック・モードのどちらかを要求している 他のトランザクションをブロックするためです。 S ロック・モードは、X ロック・モードを要求しているその他のトランザクションのみをブロックします。この小さな差が、一部のデッドロックの発生を防止するには重要です。 X ロック・モードは、最強のロック・モードです。これは、同一のマップ・エントリーに対して S、U、または X ロックの モードを取得しようとしているその他すべてのトランザクションをブロックするためにです。 X ロック・モードの最終的な効果は、1 つのトランザクションのみが マップ・エントリーを挿入、更新、または除去できるようにすることと、複数のトランザクションが同一のマップ・エントリーを更新しようとしているときに、更新が失われないようにすることです。
次表は、ロック・モードの互換性マトリックスです。 前述のロック・モードをまとめたもので、互いに互換性のあるロック・モードはいずれかを調べる場合に使用してください。 このマトリックスを読み取る場合、 マトリックスの行は既に認可されているロック・モードを表します。 列は、別のトランザクションによって要求されたロック・モードを表します。 列に「あり」と表示されている場合は、別のトランザクションによって要求されたロック・モードは認可されています。これは、既に認可されているロック・モードと互換性があるためです。「なし」は、ロック・モードの互換性がないことを表します。その他のトランザクションは、最初のトランザクションが保持しているロックを解放するのを待たなければなりません。
ロック | ロック・タイプ S (共有) | ロック・タイプ U (アップグレード可能) | ロック・タイプ X (排他的) | 強さ |
---|---|---|---|---|
S (共有) | はい | はい | いいえ | 最弱 |
U (アップグレード可能) | はい | いいえ | いいえ | 通常 |
X (排他的) | いいえ | いいえ | いいえ | 最強 |
上記のシーケンスは、2 つのトランザクションからなる古典的なデッドロックの例です。 2 つのトランザクションが複数のロックを取得しようとし、各トランザクションは異なる順序でロック取得します。このデッドロックを防止するには、各トランザクションが複数ロックを同じ順序で獲得しなければなりません。 オプティミスティック・ロック・ストラテジーが使用され、 ObjectMap インターフェースの flush メソッドがアプリケーションによって絶対に使用されない場合は、ロック・モードが トランザクションによって要求されるのはコミット・サイクル中のみです。コミット・サイクル中、eXtreme Scale は、ロックする必要があるマップ・エントリーのキーを決定し、キー・シーケンスのロック・モードを要求します (決定論的振る舞い)。この方法で、eXtreme Scale は古典的デッドロックの大多数を防止します。しかし、eXtreme Scale がすべてのデッドロック・シナリオを防止するわけでも、防止できるわけでもありません。アプリケーションが考慮する必要があるシナリオがいくつか存在します。以下は、アプリケーションが注意し、 予防アクションを取らなければならないシナリオです。
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn had a birthday, so we make her 1 year older.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();
この状況では、Lynn の知人は Lynn の年齢を加算しようとするので、Lynn とその知人が同時にこのトランザクションを実行します。この状態では、person.get("Lynn") メソッド呼び出しの結果として両方のトランザクションが PERSON マップの Lynn エントリーに対して S ロック・モードを保持します。person.put ("Lynn", p) メソッド呼び出しの結果として、両方のトランザクションは S ロック・モードを X ロック・モードに格上げしようとします。 両方のトランザクションは、 他方のトランザクションが所有している S ロック・モードを 解放するのを待つことをブロックします。 結果として、デッドロックが発生します。 2 つのトランザクション間に循環待ち条件が存在するためです。 循環待ち条件は、複数のトランザクションが同一のマップ・エントリー に対して弱いモードから強いモードへロックを格上げするときに発生します。 このシナリオでは、LockTimeoutException 例外ではなく、LockDeadlockException 例外になります。
アプリケーションは、ペシミスティック・ロック・ストラテジーではなく、オプティミスティック・ロック・ストラテジーを使用すれば、前例の LockDeadlockException 例外を防止できます。オプティミスティック・ロック・ストラテジーの使用は、 マップが主として読み取りで、マップの更新がまれにしか行われない場合、推奨される解決策です。 ペシミスティック・ロック・ストラテジーを使用する必要がある場合は、上記の例の get メソッドの代わりに、 getForUpdate メソッドを使用するか、TRANSACTION_READ_COMMITTED のトランザクション分離レベルを使用する方法があります。
詳しくは、ロック・ストラテジーを参照してください。
TRANSACTION_READ_COMMITTED トランザクション分離レベルを使用すると、通常、get メソッドによって取得される S ロックは、トランザクション完了まで保持されることがなくなります。キーがトランザクション・キャッシュで無効化されない場合、反復可能読み取りは引き続き保証されます。 詳しくは、ロック・マネージャーを参照してください。
トランザクション分離レベルを変更する方法の代替方法が、getForUpdate メソッドの使用です。getForUpdate メソッドを呼び出す最初のトランザクションは、S ロックではなく U ロック・モードを取得します。このロック・モードにより、2 番目のトランザクションは、getForUpdate メソッドを 呼び出したときにブロックされます。U ロック・モードで認可されるトランザクションは 1 つのみだからです。2 番目のトランザクションはブロックされるので、Lynn マップ・エントリーに対するロック・モードを何も所有しません。最初のトランザクションは、最初のトランザクションからの put メソッド呼び出しの結果として、 U ロック・モードから X ロック・モードへの格上げをしようとしたときに、 ブロックしません。この働きは、U ロック・モードがアップグレード可能 ロック・モードと呼ばれる理由を説明しています。 最初のトランザクションが完了すると、2 番目のトランザクションが ブロックを解除し、U ロック・モードを認可されます。 アプリケーションは、ペシミスティック・ロック・ストラテジーが使用されている場合、 get メソッドの代わりに getForUpdate メソッドを使用することにより、ロック格上げによるデッドロック・シナリオを回避できます。
あるトランザクションが複数のマップ・エントリーに対して getForUpdate メソッドを呼び出す場合、各トランザクションによって確実に U ロックが同じ順序で取得されるように注意しなければなりません。 例えば、最初のトランザクションがキー 1 に対する getForUpdate メソッドと、キー 2 に対する getForUpdate メソッドを呼び出すとします。 別の並行トランザクションが 2 つの同一キーに対する getForUpdate メソッドを呼び出しますが、逆順で呼び出します。 このシーケンスにより、古典的なデッドロックが発生します。 複数ロックが異なるトランザクションによって異なる順序で獲得されるためです。 アプリケーションでは引き続き、 複数のマップ・エントリーにアクセスするどのトランザクションもキー・シーケンスに従い、デッドロックを発生しないようにする必要があります。 U ロックはコミット時ではなく、getForUpdate メソッドが呼び出される時に獲得されるので、eXtreme Scale は、 コミット・サイクル中に行われるようにロック要求を順序付けることはできません。 アプリケーションは、このケースではロックの順序付けを制御する必要があります。
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
boolean activeTran = false;
try
{
sess.begin();
activeTran = true;
Person p = (IPerson)person.get("Lynn");
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
person.flush();
...
p = (IPerson)person.get("Tom");
p.setAge( p.getAge() + 1 );
sess.commit();
activeTran = false;
}
finally
{
if ( activeTran ) sess.rollback();
}
flush の実行時に "Lynn" のトランザクション 1 に対して X ロックが認可されます。
flush の実行時に "Tom" のトランザクション 2 に対して X ロックが認可されます。
コミット処理中に "Tom" のトランザクション 1 によって、X ロックが要求されます。
(トランザクション 1 は、
トランザクション 2 によって所有されたロックを待機するのをブロックします。)
コミット処理中に "Lynn" のトランザクション 2 によって、X ロックが要求されます。
(トランザクション 2 は、
トランザクション 1 によって所有されたロックを待機するのをブロックします。)
この例は、flush メソッドの使用により、eXtreme Scale 内ではなくデータベース内で デッドロックが発生することを示しています。 このデッドロック例は、 どのロック・ストラテジーを使用しても発生する可能性があります。 アプリケーションは、flush メソッドを使用しているときと、 Loader が BackingMap にプラグインされているときは、 この種のデッドロックの発生を防止することに留意する必要があります。 上記の例は、eXtreme Scale がロック待ちタイムアウト機構を備えている もう 1 つの理由を示しています。データベース・ロックを待機するトランザクションは、 eXtreme Scale マップ・エントリーのロックを所有している間、待機し続ける可能性があります。その結果、データベース・レベルの問題により、eXtreme Scale ロック・モードの待機時間が過大になり、LockTimeoutException 例外が発生する可能性があります。