Bloqueos

Los bloqueos tienen ciclos de vida y tipos de bloqueos diferentes son compatibles con otros de distintas formas. Los bloqueos deben manejarse en el orden correcto para evitar escenarios de punto muerto.

Bloqueos compartidos, actualizables y exclusivos

Cuando una aplicación llama a cualquier método de la interfaz ObjectMap, utiliza los métodos de búsqueda en un índice, o realiza una consulta, eXtreme Scale intenta automáticamente adquirir un bloqueo para la entrada de correlación a la que se está accediendo. WebSphere eXtreme Scale utiliza las siguientes modalidades de bloqueo basadas en el método al que llama la aplicación en la interfaz ObjectMap.

A partir de las definiciones anteriores, es obvio que una modalidad de bloqueo S es más débil que una modalidad de bloqueo U ya que permite que se ejecuten simultáneamente más transacciones al acceder a la misma entrada de correlación. La modalidad de bloqueo U es ligeramente más restrictiva que la modalidad de bloqueo S ya que bloquea las otras transacciones que soliciten una modalidad de bloqueo U o X. La modalidad de bloqueo S sólo bloquea a las otras transacciones que soliciten una modalidad de bloqueo X. Este pequeña diferencia es importante para evitar situaciones de punto muerto. La modalidad de bloqueo X es la más fuerte porque bloquea todas las otras transacciones que intenten obtener una modalidad de bloqueo S, U o X para la misma entrada de correlación. La modalidad de bloqueo X garantiza que sólo una transacción pueda insertar, actualizar o eliminar una entrada de correlación. De este modo, se evita que se pierdan actualizaciones cuando más de una transacción intenta actualizar la misma entrada de correlación.

En la tabla siguiente se ofrece una matriz de compatibilidad de modalidades de bloqueo que resume las modalidades de bloqueo descritas, que puede utilizar para determinar las modalidades de bloqueo que son compatibles entre sí. La fila de la matriz indica una modalidad de bloqueo que ya se ha otorgado. La columna indica la modalidad de bloqueo que solicita otra transacción. Si en la columna aparece Sí, se otorga la modalidad de bloqueo solicitada por otra transacción porque es compatible con la modalidad de bloqueo que ya se ha otorgado. Si aparece No, indica que la modalidad de bloqueo no es compatible y, por tanto, la otra transacción debe esperar a que la primera transacción libere el bloqueo.

Tabla 1. Matriz de compatibilidad de modalidad de bloqueo
Bloqueo Tipo de bloqueo S (compartido) Tipo de bloqueo U (actualizable) Tipo de bloqueo X (exclusivo) Fuerza
S (compartido) No más débil
U (actualizable) No No normal
X (exclusivo) No No No más fuerte

Puntos muertos de bloqueo

Considere la siguiente secuencia de peticiones de modalidad de bloqueo:
  1. Se otorga el bloqueo X a la transacción 1 para key1.
  2. Se otorga el bloqueo X a la transacción 2 para key2.
  3. La transacción 1 solicita el bloqueo X para key2. (La transacción 1 se bloquea a la espera del bloqueo en propiedad de la transacción 2).
  4. La transacción 2 solicita el bloqueo X para key1. (La transacción 2 se bloquea a la espera del bloqueo en propiedad de la transacción 1).

La secuencia anterior es el ejemplo clásico de punto muerto en el que dos transacciones intentan adquirir más de un solo bloqueo, y cada una de ellas adquiere los bloqueos en un orden diferente. Para evitar esta situación de punto muerto, cada transacción debe obtener los diversos bloqueos en el mismo orden. Si se utiliza la estrategia de bloqueo OPTIMISTIC y la aplicación nunca utiliza el método flush de la interfaz ObjectMap, la transacción sólo solicita las modalidades de bloqueo durante el ciclo de confirmación. Durante este ciclo, eXtreme Scale determina las claves de las entradas de correlación que deben bloquearse y solicita las modalidades de bloqueo en la secuencia de claves (comportamiento determinista). Con este método, eXtreme Scale evita la gran mayoría de los puntos muertos clásicos. No obstante, eXtreme Scale no puede evitar todos los escenarios posibles de punto muerto. Existen unos pocos escenarios que la aplicación debe tener en cuenta. A continuación se muestran algunos de éstos para que la aplicación pueda tomar acciones preventivas.

Se da un escenario en el que eXtreme Scale puede detectar un punto muerto sin tener que esperar a que se produzca un tiempo de espera de bloqueo. Si se da este escenario, se produce una excepción com.ibm.websphere.objectgrid.LockDeadlockException. Observe el fragmento de código siguiente:
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Ha sido el cumpleaños de Lynn, por lo que es 1 año mayor.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();

En esta situación, el novio de Lynn quiere que sea mayor de lo que lo es ahora y tanto Lynn, como su novio ejecutan esta transacción simultáneamente. En esta situación, ambas transacciones poseen una modalidad de bloqueo S en la entrada de Lynn de la correlación PERSON como resultado de la invocación del método person.get("Lynn"). Como resultado de la llamada del método person.put ("Lynn", p), ambas transacciones intentan actualizar la modalidad de bloqueo S a una modalidad de bloqueo X. Las dos transacciones se bloquean a la espera de que la otra transacción libere la modalidad de bloqueo S. Por lo tanto, se produce un punto muerto al darse una condición de espera circular entre las dos transacciones. Esta condición de espera circular se produce cuando más de una transacción intenta promover un bloqueo de una modalidad más débil a una más fuerte para la misma entrada de correlación. En este escenario, se produce una excepción LockDeadlockException en lugar de una excepción LockTimeoutException.

En el ejemplo anterior, la aplicación puede evitar la excepción LockDeadlockException si utiliza una estrategia de bloqueo optimista en lugar de la estrategia de bloqueo pesimista. El uso de una estrategia de bloqueo optimista es la solución preferida cuando básicamente se realizan lecturas en la correlación, y las actualizaciones no son frecuentes. Si debe utilizarse la estrategia de bloqueo pesimista, utilice el método getForUpdate en lugar del método get del ejemplo anterior o un nivel de aislamiento de transacción de TRANSACTION_READ_COMMITTED.

Consulte Estrategias de bloqueo para obtener más detalles.

El uso del nivel de aislamiento de la transacción TRANSACTION_READ_COMMITTED impide que se obtenga el bloqueo S adquirido por el método get hasta que se complete la transacción. Si nunca se invalida la clave en la memoria caché transaccional, se siguen garantizando las lecturas repetibles. Consulte el apartado Gestor de bloqueo para obtener más información.

Un procedimiento alternativo para cambiar el nivel de aislamiento de la transacción es utilizar el método getForUpdate. La primera transacción que llama al método getForUpdate adquiere una modalidad de bloqueo U en lugar de un bloqueo S. Esta modalidad de bloqueo hace que se bloquee la segunda transacción al llamar al método getForUpdate porque sólo se otorga una modalidad de bloqueo U a una transacción. Puesto que la segunda transacción está bloqueada, no posee ninguna modalidad de bloqueo en la entrada de la correlación de Lynn. La primera transacción no se bloquea cuando intenta actualizar la modalidad de bloqueo U a una modalidad de bloqueo X como resultado de la llamada de método put de la primera transacción. Esta característica demuestra por qué la modalidad de bloqueo U se llama actualizable. Cuando se completa la primera transacción, la segunda transacción se desbloquea y se le otorga la modalidad de bloqueo U. Cuando se utiliza una estrategia de bloqueo pesimista, una aplicación puede evitar que se produzca un escenario de punto muerto de promoción de bloqueo si utiliza el método getForUpdate en lugar del método get.

Importante: esta solución no impide que las transacciones de sólo lectura puedan leer una entrada de correlación. Las transacciones de sólo lectura llaman al método get, pero nunca llaman a los métodos put, insert, update ni remove. La simultaneidad es tan alta como cuando el utiliza el método get. La única reducción en la simultaneidad se produce cuando más de una transacción llama al método getForUpdate para la misma entrada de correlación.

Debe saber cuándo una transacción llama al método getForUpdate para más de una entrada de correlación y así garantizar que cada transacción adquiera los bloqueos U en el mismo orden. Por ejemplo, suponga que la primera transacción llama al método getForUpdate para la clave 1 y al método getForUpdate para la clave 2. Otra transacción simultánea llama al método getForUpdate para las mismas claves, pero en orden inverso. Esta secuencia dará lugar a una situación clásica de punto muerto ya que varias transacciones obtienen bloqueos en distinto orden. La aplicación debe asegurarse de que cada transacción accede a varias entradas de correlación en la secuencia de claves para garantizar que no se produzca un punto muerto. Como se obtiene el bloqueo U en el momento en el que se llama al método getForUpdate en lugar de en el ciclo de confirmación, eXtreme Scale no puede ordenar las solicitudes de bloqueo como lo hace durante el ciclo de confirmación. La aplicación debe controlar el orden de los bloqueos en este caso.

El uso del método flush de la interfaz ObjectMap antes de una confirmación presenta consideraciones de orden de bloqueos adicionales. El método flush suele utilizarse para forzar cambios realizados en la correlación para el programa de fondo a través del plug-in Loader. En esta situación, el programa de fondo utiliza su propio gestor de bloqueos para controlar la simultaneidad, de modo que la condición de espera de bloqueos y el punto muerto pueden producirse en el programa de fondo en lugar de en el gestor de bloqueos de eXtreme Scale. Observe la transacción siguiente:
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();
}
Suponga que otra transacción también ha actualizado la persona Tom, llamó al método flush y, a continuación, actualizó la persona Lynn. Si se produjera esta situación, el intercalado de las dos transacciones provocaría una condición de punto muerto de la base de datos:
Se otorga el bloqueo X a la transacción 1 para "Lynn" cuando se ejecuta el
método flush.
Se otorga el bloqueo X a la transacción 2 para "Tom" cuando se ejecuta el
método flush.
La transacción 1 solicita el bloqueo X para "Tom" durante el proceso de
confirmación. (La transacción 1 se bloquea a la espera del bloqueo en propiedad
de la transacción 2). El bloqueo X solicitado por la transacción 2 para "Lynn"
durante el proceso de confirmación.
(La transacción 2 se bloquea a la espera
del bloqueo en propiedad de la transacción 1).

Este ejemplo demuestra que el uso del método flush puede causar un punto muerto en la base de datos en lugar de en eXtreme Scale. Este ejemplo de punto muerto puede ocurrir independientemente del tipo de estrategia de bloqueo utilizado. La aplicación debe evitar que se produzca este punto muerto al utilizar el método flush y cuando un objeto Loader se conecta a BackingMap. El ejemplo anterior también ilustra otra razón por la que eXtreme Scale tiene un mecanismo de tiempo de espera de bloqueo. Una transacción que espera un bloqueo de la base de datos podría estar esperando mientras posee un bloqueo de entrada de correlación de eXtreme Scale. En consecuencia, los problemas a nivel de base de datos pueden ocasionar tiempos de espera excesivos para una modalidad de bloqueo de eXtreme Scale y terminar en una excepción LockTimeoutException.