Les verrous comportent des cycles de vie et leurs différents types sont compatibles entre eux selon plusieurs critères. Les verrous doivent être traités dans un ordre approprié pour éviter les situations d'interblocage.
Eu égard aux définitions précédentes, il est évident qu'un mode de verrouillage S est plus faible qu'un mode de verrouillage U car ce mode permet l'exécution simultanée d'un nombre plus important de transactions lors de l'accès à la même entrée de mappe. Le mode de verrouillage U est légèrement plus fort que le mode de verrouillage S lock car il bloque les autres transactions qui demandent un mode de verrouillage U ou X. Le mode de verrouillage S bloque uniquement les autres transactions qui demandent un mode de verrouillage X. Cette petite différence est pourtant importante lorsqu'il s'agit d'empêcher l'occurrence de certaines interblocages. Le mode de verrouillage X est le mode de verrouillage le plus fort car il bloque toutes les autres transactions en tentant d'obtenir un mode de verrouillage S, U ou X pour la même entrée de mappe. L'effet d'un mode de verrouillage X consiste à garantir qu'une seule transaction peut insérer, mettre à jour ou supprimer une entrée de mappe et à empêcher la perte des mise à jour lorsque plusieurs transactions tentent de mettre à jour la même entrée de mappe.
Le tableau ci-dessous est une matrice de compatibilité des modes de verrouillage qui résume les modes de verrouillage décrits et qui sert à déterminer la compatibilité entre les différents modes. Pour lire cette matrice, la ligne dans la matrice indique un mode de verrouillage déjà octroyé. La colonne décrit le mode de verrouillage demandé par une autre transaction. Si la valeur Oui s'affiche dans la colonne, cela signifie que le mode de verrouillage demandé par l'autre transaction est octroyé car il est compatible avec celui qui a déjà été octroyé. La valeur Non indique que le mode de verrouillage n'est pas compatible et que l'autre transaction doit attendre que la première transaction libère le verrou dont elle est propriétaire.
Verrou | Verrou type S (partagé) | Verrou type U (pouvant être mis à niveau) | Verrou type X (exclusif) | Puissance |
---|---|---|---|---|
S (partagé) | Oui | Oui | Non | le plus faible |
U (pouvant être mis à niveau) | Oui | Non | Non | normal |
X (exclusif) | Non | Non | Non | le plus fort |
La séquence précédente est l'exemple type d'un interblocage de deux transactions qui tentent d'acquérir plusieurs verrous et des transactions qui obtiennent les verrous dans un ordre différent. Pour éviter cet interblocage, chaque transaction doit obtenir plusieurs verrous dans le même ordre. Si la stratégie de verrouillage OPTIMISTE est utilisée et la méthode flush sur l'interface ObjectMap n'est jamais utilisée par l'application, les modes de verrouillage sont demandés par la transaction uniquement lors du cycle de validation. Pendant le cycle de validation, eXtreme Scale détermine les clés pour les entrées de mappe qui doivent être verrouillées et demande les modes de verrouillage en une séquence de clés (comportement déterministe). Avec cette méthode, eXtreme Scale évite la majorité des interblocages classiques. Toutefois, eXtreme Scale n'empêche pas et ne peut pas empêcher tous les interblocages. Quelques scénarios doivent être pris en compte par l'application. Voici les cas dont l'application doit tenir compte et pour lesquels elle doit prendre des mesures préventives.
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn a fêté son anniversaire, donc nous l'avons vieilli d'1 an.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();
Dans cette situation, l'ami de Lynn veut la vieillir d'un an et Lynn et son ami exécutent cette transaction simultanément. Dans cette situation, les deux transactions possèdent un mode de verrou S sur l'entrée Lynn de la mappe PERSON suite à l'appel de la méthode person.get("Lynn"). En raison de l'appel de la méthode person.put ("Lynn", p), les deux transactions tentent de mettre à niveau le mode de verrou S vers un mode de verrou X. Les deux transactions se bloquent dans l'attente de libération du mode de verrou S par l'autre transaction. Par conséquent, une interblocage se produit car une condition d'attente circulaire existe entre les deux transactions. Une condition d'attente circulaire a lieu lorsque plusieurs transactions tentent de promouvoir un verrou d'un mode faible vers un mode fort pour la même entrée de mappe. Dans ce scénario, une exception LockDeadlockException est émise au lieu d'une exception LockTimeoutException.
L'application peut empêcher l'exception LockDeadlockException pour l'exemple précédent à l'aide de la stratégie de verrouillage optimiste au lieu de la stratégie de verrouillage pessimiste. La stratégie de verrouillage optimiste constitue la solution privilégiée lors que la mappe est essentiellement lue et que les mises à jour ne sont pas fréquentes. Si la stratégie de verrouillage pessimiste doit être utilisée, la méthode getForUpdate peut être utilisée à la place de la méthode get de l'exemple ci-dessus ou un niveau d'isolement de transaction TRANSACTION_READ_COMMITTED peut être utilisé.
Voir Stratégies de verrouillage pour plus d'informations.
L'utilisation du niveau d'isolement de transaction TRANSACTION_READ_COMMITTED empêche le verrou S acquis par la méthode get d'être maintenu jusqu'à la fin de la transaction. Si la clé n'est jamais invalidée dans le cache transactionnel, les lectures reproductibles sont toujours garanties. Pour plus d'informations, voir Gestionnaire de verrous.
Pour changer le niveau d'isolement, vous pouvez aussi utiliser la méthode getForUpdate. La première transaction pour appeler la méthode getForUpdate obtient un mode de verrouillage U et non un mode de verrouillage S. Ce mode de verrouillage entraîne le blocage de la deuxième transaction lors de l'appel de la méthode getForUpdate car un mode de verrouillage U n'est octroyé qu'à une seule transaction. En raison du blocage de la deuxième transaction, cette dernière ne possède aucun mode de verrouillage sur l'entré de mappe Lynn. La première transaction ne se bloque pas lorsqu'elle tente de mettre à niveau le mode de verrouillage U vers le mode de verrouillage X après l'appel de la méthode put à partir de la première transaction. Cette fonction démontre pourquoi le mode de verrouillage U est appelé le mode de verrouillage pouvant être mis à niveau. Lorsque la première transaction est terminée, la deuxième se débloque et le mode de verrouillage U lui est octroyé. Une application peut empêcher le scénario d'interblocage de la promotion de verrouillage en utilisant la méthode getForUpdate au lieu de la méthode get en cas d'utilisation de la stratégie de verrouillage pessimiste.
Vous devez garder cela à l'esprit lorsqu'une transaction appelle la méthode getForUpdate dans plusieurs entrées de mappe pour garantir que les verrous U sont acquis dans le même ordre par chaque transaction. Par exemple, supposons que la première transaction appelle la méthode getForUpdate pour la clé 1 et la méthode getForUpdate pour la clé 2. Une autre transaction simultanée appelle la méthode getForUpdate pour les mêmes clés mais dans l'ordre inverse. Cette séquence entraîne l'interblocage classique car plusieurs verrous sont obtenus dans des ordres différents par diverses transactions. L'application doit toujours vérifier que chaque transaction accède à plusieurs entrées de mappe dans la séquence de clés pour éviter tout interblocage. Etant donné que le verrou U est obtenu simultanément à l'appel de la méthode getForUpdate et non au moment de la validation, eXtreme Scale ne peut pas ordonner les demandes de verrouillage de la même manière que lors du cycle de validation. L'application doit contrôler l'ordre de verrouillage dans ce cas.
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();
}
Verrou X octroyé à la transaction 1 pour "Lynn" lorsque la méthode flush est exécutée.
Verrou X octroyé à la transaction 2 pour "Tom" lorsque la méthode flush est exécutée.
Verrou X demandé par la transaction 1 pour "Tom" pendant le traitement commit.
(La transaction 1 se bloque en attente du verrou acquis par la transaction 2.)
Verrou X demandé par la transaction 2 pour "Lynn" pendant le traitement commit.
(La transaction 2 se bloque en attente du
verrou acquis par la transaction 1.)
Cet exemple montre que l'utilisation de la méthode flush peut provoquer un interblocage dans la base de données plutôt que dans eXtreme Scale. Cet exemple d'interblocage peut se produire indépendamment de la stratégie de verrouillage utilisée. L'application doit veiller à empêcher ce type d'interblocage lors de l'utilisation de la méthode flush et lorsqu'un chargeur est connecté à la mappe de sauvegarde. L'exemple précédent illustre également pourquoieXtreme Scale comporte un mécanisme de délai d'attente de verrou. Une transaction qui attend un verrou de base de données peut patienter alors qu'elle possède un verrou d'entrée de mappe eXtreme Scale. Par conséquent, des problèmes rencontrés au niveau de la base de données peuvent causer des temps d'attente excessifs pour un mode de verrou eXtreme Scale et entraîner une exception LockTimeoutException.