Mutex - Exclusão mútua em Java para controle transacional absoluto
Autor: Wiliam Silva | Publicado: 2014-04-01T00:02:00-03:00

Mutex é uma abreviação de exclusão mútua em inglês (mutual exclusion). Seu propósito é evitar que duas ou mais threads acessem simultaneamente o mesmo recurso compartilhado.

Para exemplificar um problema de falta de Mutex, imagine um sistema central multi thread responsável por apoiar o processamento de pedidos de um portal de compras. Neste cenário, o usuário pode clicar duas vezes e submeter duas vezes a requisição por uma questão de impaciência, ainda a rede de comunicação pode conter falha que redunde a requisição ou a aplicação servidora pode conter algum bug capaz de emitir mais de uma vez a requisição para o sistema de processamento final.

Se você for um programador desatento pode não acreditar na possibilidade de uma requisição redundante ocasionar impacto financeiro ao usuário e até mesmo a instituição responsável pelo portal sem contar o transtorno para administrar o problema.

Para todo efeito, o problema existe e somente um controle transacional delegado a um banco de dados não resolve porque uma nova transação em banco de dados só sabe da existência de uma anterior se esta (anterior) sofrer uma confirmação ou commit. O controle transacional via banco de dados só surtirá efeito se as requisições redundantes fossem em sequencia, mas a realidade de requisições redundantes é em grande parte paralela.

A esta altura a exclusão mútua deve fazer maior sentido caso você não a tenha compreendido.

O Mutex normalmente é aplicado no serviço central (aplicação) para impedir justamente impactos gerados por falhas em algum sistema intermediário (ex.: web-container) ou sistema terminal (ex.: browser ou usuário).

Construíndo uma controladora Mutex

A fim de tornar a Mutex mais eficiente, vamos fazer o uso da coleção com limpeza automática por tempo de espera chamada WTConcurrentLinkedQueue publicada no artigo Java - Limpeza automática de coleções. Essa classe possui dependência da classe WTTimeObject também publicada no mesmo artigo.

Agora vamos ao exemplo de uma classe para controle Mutex.

import java.io.IOException;public class MutexControl {&nbsp;&nbsp;&nbsp; private static Integer LOCKTIMEOUT_SECS = 120;&nbsp;&nbsp;&nbsp; private static String CARDPREFIX = "_CARD";&nbsp;&nbsp;&nbsp; private static String TRANSACTIONPREFIX = "_TRANSACTION";&nbsp;&nbsp;&nbsp; private static MutexControl mutexControl;&nbsp;&nbsp;&nbsp; static {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mutexControl = null;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; private static synchronized MutexControl getInstance() {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (mutexControl == null) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; mutexControl = new MutexControl();&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return mutexControl;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; private WTConcurrentHashMap&lt;String, Long&gt; list;&nbsp;&nbsp;&nbsp; private MutexControl() {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; list = new WTConcurrentHashMap&lt;String, Long&gt;("MutexControl");&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public WTConcurrentHashMap&lt;String, Long&gt; getList() {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return list;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; private static synchronized void lock(String id, Integer expirationSecs) throws IOException {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Long obj = getInstance().getList().get(id);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (obj != null) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (obj &lt; System.currentTimeMillis()) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; getInstance().getList().remove(id);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } else {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; throw new IOException(String.format("A chave de operação (%s) simultânea está em uso.", id));&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Long timeout = System.currentTimeMillis() + (expirationSecs * 1000L); &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; getInstance().getList().put(id, timeout);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; private static synchronized void unlock(String id) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; getInstance().getList().remove(id);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public static void cardLock(Long cardLogicalNumber) throws IOException {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String key = String.format("%s%d", CARDPREFIX, cardLogicalNumber);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; lock(key, LOCKTIMEOUT_SECS);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public static void cardUnlock(Long cardLogicalNumber) throws IOException {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String key = String.format("%s%d", CARDPREFIX, cardLogicalNumber);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; unlock(key);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public static void transactionLock(Long transactionId) throws IOException {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String key = String.format("%s%d", TRANSACTIONPREFIX, transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; lock(key, LOCKTIMEOUT_SECS);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public static void transactionUnlock(Long transactionId) throws IOException {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String key = String.format("%s%d", TRANSACTIONPREFIX, transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; unlock(key);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public static void main(String[] args) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /****************************************************&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;* Testar bloqueio/desbloqueio de um fictício cartão por seu id&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;****************************************************/&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Long cardId = 123L;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 1: Bloqueia cartão - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cardLock(cardId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 1: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 2: Bloqueia cartão - Resultado esperado: Não deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cardLock(cardId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 2: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 3: Desbloqueia cartão - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Se não desbloquear até 120 segundos, desbloqueia automaticamente&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Leia a constante LOCKTIMEOUT_SECS &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cardUnlock(cardId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 3: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 4: Bloqueia cartão - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cardLock(cardId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 4: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /****************************************************&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;* Testar bloqueio/desbloqueio de uma fictícia transação por seu id&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;****************************************************/&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Long transactionId = 1002L;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 5: Bloqueia transação - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; transactionLock(transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 5: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 6: Bloqueia transação - Resultado esperado: Não deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; transactionLock(transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 6: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 7: Desbloqueia transação - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Se não desbloquear até 120 segundos, desbloqueia automaticamente&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Leia a constante LOCKTIMEOUT_SECS &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; transactionUnlock(transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 7: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Teste 8: Bloqueia transação - Resultado esperado: Deve pemitir&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; try {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; transactionLock(transactionId);&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } catch (IOException e) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; System.err.println("Teste 8: " + e.getMessage());&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }}

A classe MutexControl contém como principais funções lock e unlock para bloquear um recurso pelo seu identificador. Para facilitar o entendimento, na classe ainda foram adicionadas funções de bloqueio e desbloqueio mais concretas, são os casos das funções cardLock, cardUnlock, transactionLock e transactionUnlock. Basicamente você pode criar funções de bloqueio e desbloqueio de outros recursos diretos ao utilizar a mesma técnica de criar as funções baseadas num prefixo para o identificador para não conflitar com controles de outros recursos.

Para evitar incidentes sérios caso você se esqueça de desbloquear um recurso após a operação ou mesmo que seu controle de exceção deixe o recurso bloqueado por erro, foi determinado um limite de tempo de bloqueio em 120 segundos na constante LOCKTIMEOUT_SECS da classe MutexControl. O parâmetro pode ser alterado a seu critério de tolerância para desbloquear um recurso esquecido no Mutex.

Teste da controladora Mutex

O teste está na mesma classe controladora no método main. Execute a classe em modo de debug preferencialmente e acompanhe os exemplos de controle de operação de um cartão de id 123 e em seguida, de uma transação de id 1002.

Resultado

Na tentativas de bloqueios redundantes, devem ocorrer as seguintes exceções:

Teste 2: A chave de operação (_CARD123) simultânea está em uso.Teste 6: A chave de operação (_TRANSACTION1002) simultânea está em uso.

Conclusão

Análogo a um controle de semáforo onde se passa apenas um carro por vez numa via, o Mutex é uma técnica simples e segura para a exclusão mútua de processamento redundante provocado por falhas de requisições redundantes;

Até o próximo artigo.