Bloqueios têm ciclos de vida e os tipos diferentes de bloqueios são compatíveis com outros de várias maneiras. Os bloqueios devem ser manipulados na ordem correta para evitar cenários de conflito.
Das definições anteriores, é óbvio que um modo de bloqueio S é mais fraco que um modo de bloqueio U, porque permite que mais transações sejam executadas simultaneamente durante o acesso à mesma entrada do mapa. O modo de bloqueio U é um pouco mais forte do que o modo de bloqueio S, porque bloqueia outras transações que estão solicitando um modo de bloqueio U ou X. O modo de bloqueio S bloqueia apenas outras transações que estão solicitando um modo de bloqueio X. Esta pequena diferença é importante na prevenção de alguns conflitos. O modo de bloqueio X é o modo de bloqueio mais forte, porque bloqueia todas as demais transações que estão tentando obter um modo de bloqueio S, U ou X para a mesma entrada do mapa. O efeito de rede de um modo de bloqueio X é para garantir que apenas uma transação possa inserir, atualizar ou remover uma entrada de mapa e para evitar que atualizações sejam perdidas quando mais de uma transação esteja tentando atualizar a mesma entrada de mapa.
A tabela a seguir é uma matriz de compatibilidade de modo de bloqueio que resume os modos de bloqueio descritos, que você pode utilizar para determinar quais modos de bloqueio são compatíveis com outros. Para ler esta matriz, a linha na matriz indica um modo de bloqueio já concedido. A coluna indica o modo de bloqueio solicitado por outra transação. Se Yes for exibido na coluna, então, o modo de bloqueio solicitado por outra transação é concedido porque é compatível com o modo de bloqueio que já foi concedido. No, indica que o modo de bloqueio não é compatível e a outra transação deve esperar a primeira transação liberar o bloqueio que ela possui.
Bloqueio | Tipo de bloqueio S (compartilhado) | Tipo de bloqueio U (para upgrade) | Tipo de bloqueio X (exclusivo) | Força |
---|---|---|---|---|
S (compartilhado) | Sim | Sim | Não | mais fraco |
U (para upgrade) | Sim | Não | Não | normal |
X (exclusivo) | Não | Não | Não | mais forte |
A sequência anterior é o exemplo clássico de conflito de duas transações que tentam adquirir mais de um bloqueio único e cada transação adquire os bloqueios em uma ordem diferente. Para evitar este conflito, cada transação deve obter vários bloqueios na mesma ordem. Se a estratégia de bloqueio OPTIMISTIC for utilizada e o método flush na interface ObjectMap nunca for utilizado pelo aplicativo, os modos de bloqueio serão solicitados pela transação apenas durante o ciclo de confirmação. Durante o ciclo de commit, o eXtreme Scale determina as chaves para as entradas de mapa que precisam ser bloqueadas e solicita os modos de bloqueio na sequência de chaves (comportamento determinístico). Com este método, o eXtreme Scale evita a grande maioria dos conflitos clássicos. Entretanto, o eXtreme Scale não evita e não pode evitar todos os cenários de conflito possíveis. Existem poucos cenários que o aplicativo precisa considerar. A seguir estão os cenários que o aplicativo deve considerar e executar uma ação preventiva contra.
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();
Nessa situação, o namorado de Lynn quer que ela seja mais velha do que sua idade atual e tanto Lynn quanto seu namorado executam essa transação simultaneamente. Nesta situação, as duas transações possuem um modo de bloqueio S na entrada Lynn do mapa PERSON como resultado da chamada de método person.get("Lynn"). Como resultado da chamada de método person.put ("Lynn", p), as duas transações tentam fazer upgrade do modo de bloqueio S para um modo de bloqueio X. As duas transações bloqueiam a espera para que a outra transação libere o modo de bloqueio S que ela possui. Como resultado, ocorre um conflito porque existe uma condição de espera circular entre as duas transações. É gerada uma condição de espera circular quando mais de uma transação tenta promover um bloqueio de um modo mais fraco para um mais forte para a mesma entrada do mapa. Neste cenário, o resultado é uma exceção LockDeadlockException ao invés de uma exceção LockTimeoutException.
O aplicativo pode evitar a exceção LockDeadlockException para o exemplo anterior utilizando a estratégia de bloqueio optimistic ao invés da estratégia de bloqueio pessimistic. Utilizar a estratégia de bloqueio optimistic é a solução preferida quando o mapa é em maior parte lido e as atualizações no mapa são infrequentes. Se a estratégia de bloqueio pessimistic deve ser utilizada, o método getForUpdate pode ser utilizado ao invés do método get no exemplo acima ou pode ser utilizado um nível de isolamento de transação de TRANSACTION_READ_COMMITTED.
Consulte o Estratégias de Bloqueio para obter mais detalhes.
Utilizar o nível de isolamento da transação TRANSACTION_READ_COMMITTED evita o bloqueio S adquirido pelo método get seja mantido até a transação ser concluída. Como a chave nunca é invalidada no cache transacional, as leituras repetitíveis ainda são garantidas. Consulte Gerenciador de Bloqueio para obter informações adicionais.
Uma alternativa para alterar o nível de isolamento de transação é utilizar o método getForUpdate. A primeira transação para chamar o método getForUpdate adquire um modo de bloqueio U ao invés de um bloqueio S. Este modo de bloqueio faz com que a segunda transação seja bloqueada quando ela chama o método getForUpdate, porque apenas uma transação recebe um modo de bloqueio U. Como a segunda transação é bloqueada, ela não possui nenhum modo de bloqueio na entrada do mapa de Lynn. A primeira transação não é bloqueada quando tenta atualizar o modo de bloqueio U para um modo de bloqueio X como um resultado da chamada de método put a partir da primeira transação. Este recurso demonstra porque o modo de bloqueio U é chamado no modo de bloqueio atualizável. Quando a primeira transação é concluída, a segunda transação é desbloqueada e recebe o modo de bloqueio U. Um aplicativo pode evitar o cenário de conflito de promoção de bloqueio utilizando o método getForUpdate ao invés do método get quando a estratégia de bloqueio pessimistic está sendo utilizada.
Você deve estar ciente de que uma transação chama o método getForUpdate em mais de uma entrada de mapa para garantir que os bloqueios U sejam adquiridos na mesma ordem para cada transação. Por exemplo, suponha que a primeira transação chame o método getForUpdate para a chave 1 e o método getForUpdate para a chave 2. Outra transação simultânea chama o método getForUpdate para as mesmas chaves, mas em ordem inversa. Esta sequência causa o conflito clássico, porque vários bloqueios são obtidos em diferentes ordens por diferentes transações. O aplicativo ainda precisará assegurar que cada transação acesse várias entradas do mapa na sequência de chaves para assegurar que não ocorrerá o conflito. Como o bloqueio U é obtido no momento em que o método getForUpdate é chamado ao invés de no momento do commit, o eXtreme Scale não pode ordenar os pedidos de bloqueio como ele faz durante o ciclo do commit. O aplicativo deve controlar a ordem de bloqueios neste caso.
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();
}
X lock is granted to transaction 1 for "Lynn" when flush is executed.
X lock is granted to transaction 2 for "Tom" when flush is executed..
X lock requested by transaction 1 for "Tom" during commit processing.
(Transaction 1 blocks waiting for lock owned by transaction 2.)
X lock requested by transaction 2 for "Lynn" during commit processing.
(Transaction 2 blocks waiting for lock owned by transaction 1.)
Este exemplo demonstra que o uso do método flush pode causar um conflito no banco de dados ao invés de no eXtreme Scale. Este exemplo de conflito pode ocorrer, independentemente da estratégia de bloqueio utilizada. O aplicativo deve ter atenção para evitar que ocorra este tipo de conflito ao utilizar o método flush e quando um Utilitário de Carga for conectado ao BackingMap. O exemplo anterior também ilustra outro motivo pelo qual o eXtreme Scale tem um mecanismo de tempo limite de espera de bloqueio. Uma transação que está aguardando um bloqueio de banco de dados poderia estar aguardando enquanto possuía um bloqueio de entrada de mapa do eXtreme Scale. Consequentemente, problemas no nível do banco de dados podem causar tempos de espera excessivos para um modo de bloqueio do eXtreme Scale e resultam em uma exceção LockTimeoutException.