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.
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
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:
Para todas as funções, os parâmetros são value e scale:
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));
}
}
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
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
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.
Marcadores:
doubleToInt,
doubleToLong,
floatToInt,
floatToShort,
flutuante para inteiro.,
java,
múltiplicar sem perder precisão,
perda de precisão na múltiplicação
Assinar:
Postagens (Atom)