StampedLock: Uma Alternativa de Alto Desempenho para Sincronização em Java

  1. Introdução ao StampedLock

No artigo anterior, discutimos os bloqueios de leitura e escrita (read-write locks) e seu uso em cenários com múltiplas leituras e poucas escritas. Mas existe uma alternativa com desempenho superior disponível desde o Java 1.8: o StampedLock.

  1. Vantagens do StampedLock

Enquanto os bloqueios de leitura e escrita tradicionalmente possuem dois modos (leitura e escrita), o StampedLock oferece três modos operacionais:

  • Bloqueio de escrita
  • Bloqueio de leitura pessimista
  • Bloqueio de leitura otimista

Os dois primeiros correspondem funcionalmente aos bloqueios de escrita e leitura tradicionais. No entanto, o recurso diferencial do StampedLock é a leitura otimista, que não é um bloqueio propriamente dito, mas sim uma operação que permite que múltiplos threads leiam simultaneamente sem adquirir bloqueios.

Para garantir segurança concorrente, a leitura otimista utiliza um "carimbo" (stamp) como sinal de segurança, similar ao mecanismo de versão usado em bloqueios otimistas de banco de dados.

  1. Diferenças entre bloqueio de escrita/leitura pessimista e bloqueios tradicionais

Quando adquiremos bloqueios de escrita ou leitura pessimista com StampedLock, o método retorna um carimbo (stamp). Para liberar o bloqueio, devemos fornecer este carimbo correspondente:

final StampedLock sl = new StampedLock();

// Exemplo de aquisição/liberação de bloqueio de leitura pessimista
long stamp = sl.readLock();
try {
    // código de negócio
} finally {
    sl.unlockRead(stamp);
}

// Exemplo de aquisição/liberação de bloqueio de escrita
long stamp = sl.writeLock();
try {
    // código de negócio
} finally {
    sl.unlockWrite(stamp);
}

  1. Utilizando a leitura otimista

O método tryOptimistic() implementa a leitura otimista. Como não envolve bloqueio, as variáveis compartilhadas podem ser modificadas por outros threads durante a leitura. Portanto, após a leitura, precisamos verificar se houve operações de escrito através do método validate(stamp).

class Ponto {
    private int x, y;
    final StampedLock sl = new StampedLock();
    
    // Calcula a distância da origem
    double distanciaDaOrigem() {
        // Leitura otimista
        long stamp = sl.tryOptimisticRead();
        
        // Lê para variáveis locais
        // Os dados podem ser modificados durante a leitura
        int currentX = x, currentY = y;
        
        // Verifica se houve operações de escrita durante a leitura
        if (!sl.validate(stamp)) {
            // Converte para bloqueio de leitura pessimista
            stamp = sl.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                // Libera bloqueio de leitura pessimista
                sl.unlockRead(stamp);
            }
        }
        
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

No código acima, se ocorrerem operações de esccrita durante a leitura otimista, ela é convertida para bloqueio de leitura pessimista. Isso evita loops infinitos e desperdício de CPU causados por tentativas repetidas de leitura otimista.

  1. Precauções ao usar StampedLock

  • Nunca interrompa threads bloqueadas em readLock() ou writeLock(): Chamar interrupt() em threads bloqueadas nessas operações pode causar problemas sérios no CPU. Se necessário usar interrupção, utilize readLockInterruptibly() e writeLockInterruptibly().
  • Funcionalidade limitada: StampedLock oferece menos funcionalidades comparado aos bloqueios de leitura e escrita tradicionais.
  • Não suporta reentrância: StampedLock não é um bloqueio reentrante, portanto não pode ser usado de forma aninhada.
  1. Modelos de implementação recomendados

Modelo de leitura com StampedLock:

final StampedLock sl = new StampedLock();

// Leitura otimista
long stamp = sl.tryOptimisticRead();
// Lê para variáveis locais
......

// Verifica o carimbo
if (!sl.validate(stamp)) {
    // Converte para bloqueio de leitura pessimista
    stamp = sl.readLock();
    try {
        // Lê para variáveis locais
        ......
    } finally {
        // Libera bloqueio de leitura pessimista
        sl.unlockRead(stamp);
    }
}
// Usa variáveis locais para operações de negócio
......

Modelo de escrita com StampedLock:

long stamp = sl.writeLock();
try {
    // Escreve variáveis compartilhadas
    ......
} finally {
    sl.unlockWrite(stamp);
}

Tags: java StampedLock Concorrência Sincronização desempenho

Publicado em 6-30 22:27