Translate

Mostrando postagens com marcador java. Mostrar todas as postagens
Mostrando postagens com marcador java. Mostrar todas as postagens

terça-feira, 1 de abril de 2014

Mutex - Exclusão mútua em Java para controle transacional absoluto

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 {

    private static Integer LOCKTIMEOUT_SECS = 120;

    private static String CARDPREFIX = "_CARD";
    private static String TRANSACTIONPREFIX = "_TRANSACTION";

    private static MutexControl mutexControl;

    static {
        mutexControl = null;
    }
   
    private static synchronized MutexControl getInstance() {
        if (mutexControl == null) {
            mutexControl = new MutexControl();
        }
        return mutexControl;
    }

    private WTConcurrentHashMap<String, Long> list;

    private MutexControl() {
        list = new WTConcurrentHashMap<String, Long>("MutexControl");
    }

    public WTConcurrentHashMap<String, Long> getList() {
        return list;
    }

    private static synchronized void lock(String id, Integer expirationSecs) throws IOException {

        Long obj = getInstance().getList().get(id);

        if (obj != null) {
            if (obj < System.currentTimeMillis()) {
                getInstance().getList().remove(id);
            } else {
                throw new IOException(String.format("A chave de operação (%s) simultânea está em uso.", id));
            }
        }

        Long timeout = System.currentTimeMillis() + (expirationSecs * 1000L);
        getInstance().getList().put(id, timeout);

    }
   
    private static synchronized void unlock(String id) {
        getInstance().getList().remove(id);
    }

    public static void cardLock(Long cardLogicalNumber) throws IOException {
       
        String key = String.format("%s%d", CARDPREFIX, cardLogicalNumber);
       
        lock(key, LOCKTIMEOUT_SECS);

    }

    public static void cardUnlock(Long cardLogicalNumber) throws IOException {

        String key = String.format("%s%d", CARDPREFIX, cardLogicalNumber);

        unlock(key);

    }

    public static void transactionLock(Long transactionId) throws IOException {
       
        String key = String.format("%s%d", TRANSACTIONPREFIX, transactionId);
       
        lock(key, LOCKTIMEOUT_SECS);

    }

    public static void transactionUnlock(Long transactionId) throws IOException {

        String key = String.format("%s%d", TRANSACTIONPREFIX, transactionId);

        unlock(key);

    }
   
    public static void main(String[] args) {

        /****************************************************
         * Testar bloqueio/desbloqueio de um fictício cartão por seu id
         ****************************************************/
        Long cardId = 123L;
       
        // Teste 1: Bloqueia cartão - Resultado esperado: Deve pemitir
        try {
            cardLock(cardId);
        } catch (IOException e) {
            System.err.println("Teste 1: " + e.getMessage());
        }

        // Teste 2: Bloqueia cartão - Resultado esperado: Não deve pemitir
        try {
            cardLock(cardId);
        } catch (IOException e) {
            System.err.println("Teste 2: " + e.getMessage());
        }

        // Teste 3: Desbloqueia cartão - Resultado esperado: Deve pemitir
        // Se não desbloquear até 120 segundos, desbloqueia automaticamente
        // Leia a constante LOCKTIMEOUT_SECS
        try {
            cardUnlock(cardId);
        } catch (IOException e) {
            System.err.println("Teste 3: " + e.getMessage());
        }
       
        // Teste 4: Bloqueia cartão - Resultado esperado: Deve pemitir
        try {
            cardLock(cardId);
        } catch (IOException e) {
            System.err.println("Teste 4: " + e.getMessage());
        }

        /****************************************************
         * Testar bloqueio/desbloqueio de uma fictícia transação por seu id
         ****************************************************/
        Long transactionId = 1002L;
       
        // Teste 5: Bloqueia transação - Resultado esperado: Deve pemitir
        try {
            transactionLock(transactionId);
        } catch (IOException e) {
            System.err.println("Teste 5: " + e.getMessage());
        }

        // Teste 6: Bloqueia transação - Resultado esperado: Não deve pemitir
        try {
            transactionLock(transactionId);
        } catch (IOException e) {
            System.err.println("Teste 6: " + e.getMessage());
        }

        // Teste 7: Desbloqueia transação - Resultado esperado: Deve pemitir
        // Se não desbloquear até 120 segundos, desbloqueia automaticamente
        // Leia a constante LOCKTIMEOUT_SECS
        try {
            transactionUnlock(transactionId);
        } catch (IOException e) {
            System.err.println("Teste 7: " + e.getMessage());
        }
       
        // Teste 8: Bloqueia transação - Resultado esperado: Deve pemitir
        try {
            transactionLock(transactionId);
        } catch (IOException e) {
            System.err.println("Teste 8: " + e.getMessage());
        }
       
    }

}


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.

segunda-feira, 28 de outubro de 2013

Java - Obter caminho da aplicação

Obter o caminho completo de uma aplicação algumas vezes é necessário para o programador poder criar, alterar, apagar ou simplesmente acessar subpastas ou arquivos em um sistema de arquivos nativo do sistema operacional. Enfatizo o sistema de arquivos do sistema operacional porque ocorre algumas vezes também a necessidade de se obter acessos de pastas ou arquivos de um JAR, porém não é alvo deste artigo isso.

A classe ApplicationPath

Esta classe contém o método estático para retornar o caminho absoluto de execução da aplicação e tem um simples método main para testar a obtenção do caminho da aplicação.

import java.io.File;
import java.io.IOException;

public class ApplicationPath {

public static String getAppPath() throws IOException  {

String result = null;

File f = new File("");

result = f.getCanonicalPath();

return result;

}

public static void main(String[] args) {

System.out.println("Caminho desta aplicação:");

try {
System.out.println(getAppPath());
} catch (IOException e) {
System.err.println("#Erro#");
}

}

}

Resultado

Caminho desta aplicação:
C:\Users\admin\workspace\Test

quarta-feira, 21 de agosto de 2013

Como multiplicar sem perder precisão

Conforme o último artigo, na plataforma Intel ocorre problema de precisão para número ponto flutuante quando se tenta multiplicar com o operador padrão "*".

Este problema é bastante severo quando o sistema trata de transações comerciais ou financeiras, porque são dois tipos de transações que não podem ter erro de precisão, senão os números não batem na contabilidade e em casos mais graves, quando se trata de sistemas de recargas, isso é mais crítico ainda porque é a população humana quem sofre o ônus da falha. Claro, existem "n" impactos.

Para contornar o problema em qualquer linguagem, basta usar a seguinte lógica:

      double tempValue = value * 100;
      tempValue = Math.floor(tempValue + 0.5);

Pronto!

Não, vamos fazer uma classe com as funções e testes dos erros e da solução:

Classe FloatToInteger

O nome da classe não tem nada a ver com os tipos numéricos do Java, é apenas uma forma de dizer que esta classe trata de transformar número flutuantes em números inteiros.

Dentro desta classe existem as seguintes funções auxiliares:

  • doubleToLong - está função converte double para long;
  • doubleToInt - está função converte double para int;
  • floatToInt - está função converte float para int;
  • floatToShort - está função converte float para short.

Para todas as funções, os parâmetros são value e scale:

  • value - é o valor flutuante;
  • scale - é o número de casas decimais e value para a função saber como transformar o flutuante em inteiro.

Código

public class FloatToInteger {

public static long doubleToLong(double value, int scale) {  

double tempValue = value * Math.pow(10, scale);
tempValue = Math.floor(tempValue + 0.5);
return (long) tempValue;

}

public static int doubleToInt(double value, int scale) {  

double tempValue = value * Math.pow(10, scale);
tempValue = Math.floor(tempValue + 0.5);
return (int) tempValue;

}

public static int floatToInt(float value, int scale) {  

double tempValue = value * Math.pow(10, scale);
tempValue = Math.floor(tempValue + 0.5);
return (int) tempValue;

}

public static short floatToShort(float value, int scale) {  

double tempValue = value * Math.pow(10, scale);
tempValue = Math.floor(tempValue + 0.5);
return (short) tempValue;

}

public static void main(String[] args) {

troubleSimulation();
solutionSimulation();

}

public static void troubleSimulation() { 

int erros = 0;

/***********************
* Provocar até 5 erros para amostragem do problema
* ao usar a múltiplicação comum
***********************/
double decimalValue = 0.01d;
long singleNumber = 0L;

System.out.println("1) AMOSTRAGEM DO PROBLEMA - Com o problema");

while (true) {

if (decimalValue >= 100.0d) {
break;
}

decimalValue += 0.10d;

singleNumber = (long) (decimalValue * 100); 

if (singleNumber != doubleToLong(decimalValue, 2)) {
if (erros <= 5) {
System.err.println(String.format("  ERROR: Original: %.20f, After: %d", decimalValue, singleNumber));
}
erros++;
}

}

System.out.println(String.format("População: %.0f, Total de erros: %d\n", 100 / 0.10, erros));

}

public static void solutionSimulation() { 

int erros = 0;

/***********************
* Provocar até 5 erros para amostragem do problema
* ao usar a múltiplicação comum
***********************/
double decimalValue = 0.01d;
long singleNumber = 0L;

System.out.println("2) AMOSTRAGEM DE PROBLEMAS - Após com a solução");

while (true) {

if (decimalValue >= 100.0d) {
break;
}

decimalValue += 0.10d;

singleNumber = doubleToLong(decimalValue, 2); 

if (singleNumber != doubleToLong(decimalValue, 2)) {
if (erros <= 100) {
System.err.println(String.format("  ERROR: Original: %.20f, After: %d", decimalValue, singleNumber));
}
erros++;
}

}

System.out.println(String.format("População: %.0f, Total de erros: %d\n", 100 / 0.10, erros));

}

}

Resultado dos testes

Observe o método main, ele invoca duas funções, uma para simular o problema e outro para comprovar a solução.

Resultado

1) AMOSTRAGEM DO PROBLEMA - Com o problema
  ERROR: Original: 0,90999999999999990000, After: 90
  ERROR: Original: 4,81000000000000000000, After: 480
  ERROR: Original: 4,90999999999999900000, After: 490
  ERROR: Original: 5,00999999999999900000, After: 500
  ERROR: Original: 5,10999999999999850000, After: 510
  ERROR: Original: 5,20999999999999800000, After: 520
População: 1000, Total de erros: 385

2) AMOSTRAGEM DE PROBLEMAS - Após com a solução
População: 1000, Total de erros: 0

Boa sorte!

Até mais.