Concorrência em Java: Como implementar StampedLocks para controle de acesso

Quando se trata de programação concorrente em Java, a sincronização eficiente entre threads é crucial para a performance e a segurança das aplicações. Além dos tradicionais synchronized
e ReentrantLock
, o Java 8 trouxe uma nova opção: o StampedLock
. Este post irá explorar o que são StampedLocks, como eles podem ser utilizados e quais vantagens eles oferecem.
O que é StampedLock?
O StampedLock é uma ferramenta de bloqueio que foi introduzida no Java 8 como parte do pacote java.util.concurrent.locks
. Diferente dos ReentrantLocks
, StampedLocks
oferecem um modelo de bloqueio baseado em estampas (stamps) que representam estados de bloqueio. Isso permite controlar leituras e escritas com mais flexibilidade e menor overhead em cenários de leitura mais frequentes.
Como funciona o StampedLock?
Ao contrário do bloqueio ReentrantLock
, o StampedLock
não é reentrante, o que significa que a mesma thread não pode adquirir um StampedLock
mais de uma vez sem liberá-lo, evitando assim o deadlock causado por uma programação descuidada. A grande vantagem dos StampedLocks é a possibilidade de converter um bloqueio de leitura em um bloqueio de escrita sem comprometer a atomicidade.
O que é atomicidade?
A atomicidade, no contexto de StampedLock
e programação concorrente, refere-se à propriedade de uma operação ser completada integralmente sem ser interrompida por outras operações. Em outras palavras, uma operação atômica ocorre como uma única unidade indivisível, sem estado intermediário visível. Isso é crucial para evitar erros e comportamentos inesperados em ambientes multithread, onde múltiplas operações podem tentar modificar o mesmo recurso simultaneamente.
Exemplo
Depositando em uma Conta
Vamos começar com um exemplo prático que demonstra como usar StampedLock
para controle de acesso a um recurso compartilhado, como um saldo de conta:
import java.util.concurrent.locks.StampedLock;
public class Conta {
private final StampedLock bloqueio = new StampedLock();
private double saldo = 0.0;
public void depositar(double valor) {
long carimbo = bloqueio.writeLock(); // Bloqueio de escrita
try {
saldo += valor;
} finally {
bloqueio.unlockWrite(carimbo); // Desbloqueio de escrita
}
}
public double obterSaldo() {
long carimbo = bloqueio.tryOptimisticRead(); // Tentativa de leitura otimista
double saldoAtual = saldo;
if (!bloqueio.validate(carimbo)) { // Valida se o carimbo ainda é válido
carimbo = bloqueio.readLock(); // Obtém um bloqueio de leitura
try {
saldoAtual = saldo;
} finally {
bloqueio.unlockRead(carimbo); // Desbloqueio de leitura
}
}
return saldoAtual;
}
}
Ajustando o Saldo
Agora, veja como um bloqueio de leitura pode ser convertido em um bloqueio de escrita, útil quando uma verificação durante a leitura determina que uma modificação é necessária:
public void ajustarSaldo(double ajuste) {
long carimbo = bloqueio.readLock(); // Bloqueio de leitura
try {
while (true) {
double saldoAtual = saldo;
double novoSaldo = saldoAtual + ajuste;
long ws = bloqueio.tryConvertToWriteLock(carimbo); // Tenta converter para bloqueio de escrita
if (ws != 0L) {
carimbo = ws;
saldo = novoSaldo;
return;
} else {
bloqueio.unlockRead(carimbo); // Desbloqueio de leitura
carimbo = bloqueio.writeLock(); // Novo bloqueio de escrita
}
}
} finally {
bloqueio.unlock(carimbo); // Desbloqueio geral
}
}
Conclusão
StampedLock é uma excelente opção ao lidar com concorrência em Java, oferecendo uma forma de controle de acesso mais granular com potencial para melhorar a performance em cenários de leitura intensiva. No entanto, é crucial entender suas particularidades e limitações, especialmente o fato de não ser reentrante, o que exige cuidado adicional para evitar deadlocks. Além disso, a capacidade de converter leituras em escritas pode ser extremamente útil em situações onde a carga de trabalho é variável e a eficiência é crítica.