Lock: o que é, tipos de bloqueios e uso em sincronização

Autor: IT Sectr Publicado: 2026-03-19 Tempo de leitura: 8 min

Lock é um mecanismo de sincronização que fornece acesso exclusivo a seções críticas de código em aplicações multithread. De acordo com a Oracle, 2024, a interface Lock oferece um controle de sincronização mais flexível em comparação com os blocos synchronized tradicionais, incluindo tentativas de aquisição com tempo limite e suporte a múltiplas filas de espera.

Principais Pontos

  • Lock é uma interface para gerenciamento explícito de bloqueios em Java.
  • ReentrantLock é uma implementação básica com suporte a reentrância pela mesma thread.
  • ReadWriteLock separa bloqueios de leitura e escrita para melhor desempenho.
  • Deadlock é o principal risco ao usar múltiplos bloqueios simultaneamente.
  • Ao contrário de synchronized, Lock suporta timeouts e espera interrompível.

O que é Lock?

Lock é uma interface do pacote java.util.concurrent.locks que fornece operações explícitas de bloqueio e desbloqueio para sincronizar o acesso a dados. Ao contrário de synchronized, Lock dá ao desenvolvedor controle total sobre o mecanismo de bloqueio.

Definição e papel na sincronização

A interface Lock foi introduzida no Java 5 como uma alternativa ao mecanismo synchronized integrado. Os principais métodos são lock, unlock, tryLock e lockInterruptibly. Os bloqueios ajudam a organizar o acesso seguro a dados em um ambiente multithread, evitando condições de corrida e corrupção de dados.

A principal vantagem do Lock sobre o synchronized é a flexibilidade. O desenvolvedor pode tentar adquirir um bloqueio com tempo limite, verificar sua disponibilidade sem bloquear ou organizar múltiplas filas de espera com diferentes prioridades.

História e evolução

Antes da interface Lock aparecer no Java 5, o único método de sincronização era o synchronized, que sofria de limitações: sem timeouts, sem espera interrompível e uma única fila. Doug Lea projetou o pacote java.util.concurrent, incluindo o Lock como um bloco fundamental.

Como funciona um bloqueio?

Um bloqueio gerencia o acesso através de uma bandeira de estado interna e uma fila de espera. Quando uma thread chama lock(), o mecanismo verifica se o bloqueio está livre e o adquire ou coloca a thread na fila até que seja liberado.

Aquisição e liberação atômica

No centro de qualquer bloqueio está uma operação atômica de comparação e troca (CAS). Ao chamar lock(), a thread tenta definir atomicamente a bandeira de ocupado. Se a bandeira já estiver definida, a thread bloqueia. Ao chamar unlock(), a bandeira é limpa e uma thread em espera é despertada.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // seção crítica
        println("A thread ${Thread.currentThread().name} está trabalhando")
    } finally {
        lock.unlock()
    }
}

Fila de espera e ativação

ReentrantLock usa internamente uma lista duplamente ligada (fila de bloqueio CLH) onde cada thread em espera é representada por um nó. Quando o bloqueio é liberado, o nó principal da fila é despertado. O modo justo (fair) garante ordenação FIFO, enquanto o modo injusto permite que uma nova thread adquira o bloqueio antes das que esperam para melhorar a vazão.

Principais tipos de bloqueios

No ecossistema Java moderno, existem várias implementações de bloqueios, cada uma otimizada para cenários específicos. Escolher o bloqueio certo impacta diretamente o desempenho e a confiabilidade de uma aplicação multithread.

ReentrantLock

ReentrantLock é a implementação básica e mais usada do Lock. Ela suporta reentrância pela mesma thread: se uma thread já possui o bloqueio, chamar lock() novamente não a bloqueia. Isso evita deadlocks em chamadas recursivas.

ReentrantReadWriteLock

ReadWriteLock separa os bloqueios em dois modos: leitura e escrita. Várias threads podem manter o bloqueio de leitura simultaneamente, mas a escrita requer acesso exclusivo. Isso melhora significativamente o desempenho sob leituras frequentes e escritas raras.

StampedLock

StampedLock é a implementação mais nova, introduzida no Java 8. Ela suporta três modos: escrita, leitura e leitura otimista. A leitura otimista não bloqueia outras threads e valida os dados após a leitura, proporcionando um ganho de desempenho de 10-20% sobre o ReadWriteLock.

BloqueioVersão JavaModosDesempenho
ReentrantLockJava 5exclusivoalto
ReadWriteLockJava 5leitura + escritamédio
StampedLockJava 8leitura + escrita + otimistamuito alto

ReentrantLock e suas características

ReentrantLock é a implementação de Lock mais popular, oferecendo vários recursos indisponíveis no synchronized. Compreender suas características é essencial para o trabalho eficaz com multithreading.

Justiça do bloqueio (fairness)

O construtor do ReentrantLock aceita um parâmetro fair. Quando true, o bloqueio garante ordem FIFO; quando false, uma nova thread pode adquirir o bloqueio antes das que esperam. O modo justo evita inanição, mas reduz a vazão em 10-20% devido à sobrecarga de manter a fila.

Timeouts e espera interrompível

Ao contrário de synchronized, ReentrantLock suporta tryLock com tempo limite. Se o bloqueio não puder ser adquirido dentro do tempo especificado, a thread continua a execução em vez de bloquear indefinidamente. O método lockInterruptibly permite interromper uma thread em espera através de Thread.interrupt().

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Bloqueio adquirido")
        } finally {
            lock.unlock()
        }
    } else {
        println("Falha ao adquirir o bloqueio")
    }
}

Condições (Conditions)

ReentrantLock suporta múltiplas variáveis de condição através do método newCondition(). Cada Condition tem sua própria fila de espera, permitindo cenários de ativação complexos. Os métodos await() e signal() substituíram wait() e notify() dos blocos synchronized, mas com suporte a múltiplas filas.

ReadWriteLock e StampedLock

ReadWriteLock e StampedLock abordam a otimização do acesso quando as leituras predominam sobre as escritas. Eles são significativamente mais eficientes que o ReentrantLock em cenários onde a leitura ocorre com mais frequência que a escrita.

ReadWriteLock na prática

A interface ReadWriteLock contém dois métodos: readLock() e writeLock(). O bloqueio de leitura pode ser mantido por várias threads simultaneamente, enquanto o bloqueio de escrita é exclusivo. Um exemplo típico é um cache thread-safe: muitas threads leem dados enquanto apenas uma os atualiza periodicamente.

kotlin
class SafeCache<K, V> {
    private val map = mutableMapOf<K, V>()
    private val rwLock = ReentrantReadWriteLock()

    fun get(key: K): V? {
        rwLock.readLock().lock()
        return try { map[key] } finally { rwLock.readLock().unlock() }
    }

    fun put(key: K, value: V) {
        rwLock.writeLock().lock()
        return try { map[key] = value } finally { rwLock.writeLock().unlock() }
    }
}

StampedLock e leitura otimista

StampedLock adiciona um terceiro modo — tryOptimisticRead. Este modo não bloqueia outras threads, mas apenas registra um carimbo (stamp) de estado. Após a leitura, o desenvolvedor chama validate(stamp) para verificar se os dados mudaram durante a leitura. Se os dados mudaram, a operação deve ser repetida.

Bloqueios no desenvolvimento móvel

Em aplicações móveis, os bloqueios são usados para coordenar o acesso a dados compartilhados entre threads. No entanto, seu uso requer cautela especial devido aos recursos limitados do dispositivo e à necessidade de manter a capacidade de resposta da interface.

Bloqueios no Android (Kotlin)

No Android, ReentrantLock é útil ao trabalhar com Room, caches e arquivos. É importante lembrar: nunca adquira um bloqueio na thread principal. Para código assíncrono, são preferíveis corrotinas e Mutex do kotlinx.coroutines, que suspendem a corrotina em vez de bloquear a thread.

Bloqueios no iOS (Swift)

No iOS, o NSLock padrão é usado com menos frequência — os desenvolvedores preferem DispatchQueue com flags barrier ou os_unfair_lock. O Swift 5.7+ fornece mecanismos modernos de sincronização através de atores, que protegem o estado automaticamente.

swift
import Foundation

actor DataStore {
    private var items: [String] = []

    func add(_ item: String) {
        items.append(item)
    }

    func getAll() -> [String] {
        items
    }
}

Recomendações para evitar deadlocks

Para evitar deadlocks, siga uma ordem consistente de aquisição em todo o projeto. Use tryLock com tempo limite em vez de lock() sempre que o bloqueio prolongado for possível. Considere usar algoritmos Lock-Free (AtomicReference, ConcurrentHashMap) em vez de bloqueios tradicionais.

Melhores práticas para trabalhar com Lock

O uso de Lock requer disciplina e adesão a várias regras que previnem deadlocks e degradação de desempenho. Essas práticas foram desenvolvidas pela comunidade Java ao longo de 20 anos de uso do pacote java.util.concurrent.

Liberação no finally

O padrão mais importante é lock no finally. Independentemente de a seção crítica ser concluída com sucesso ou lançar uma exceção, o bloqueio deve ser liberado. Isso garante que outras threads não fiquem bloqueadas para sempre devido a um único erro. Em Kotlin, esse padrão é elegantemente resolvido através da extensão withLock.

Minimizar o tempo de retenção

A seção crítica deve ser o mais curta possível. Nunca realize E/S, solicitações de rede ou cálculos prolongados dentro de um bloqueio. Se precisar ler dados de um servidor, obtenha-os primeiro e adquira o bloqueio apenas para atualizar o estado compartilhado. Isso reduz a contenção e melhora a vazão do sistema.

Ordem de aquisição consistente

Para prevenir deadlocks ao trabalhar com múltiplos bloqueios, estabeleça uma ordem global de aquisição em todo o projeto. Se lockA for adquirido primeiro, depois lockB — qualquer sequência inversa deve ser proibida pelas regras de revisão de código. Use analisadores estáticos como SpotBugs e IntelliJ Inspections para verificação automática.

Perguntas Frequentes

Qual é a diferença entre Lock e synchronized?

Lock é uma interface explícita com suporte a tempo limite e espera interrompível. synchronized adquire e libera automaticamente o monitor, mas não permite usar tryLock, lockInterruptibly ou múltiplas Conditions. Lock é mais flexível, mas requer liberação manual no finally.

O que é um bloqueio justo (fair lock)?

Um bloqueio justo garante ordem FIFO: a thread que esperou mais tempo recebe o bloqueio primeiro. Um bloqueio injusto pode conceder acesso a uma nova thread antes das que esperam, o que aumenta a vazão, mas pode causar inanição das threads em espera.

Como evitar deadlock ao usar Lock?

Siga uma ordem fixa para adquirir todos os bloqueios, use tryLock com tempo limite em vez de lock() incondicional e minimize o número de bloqueios mantidos simultaneamente. O uso de estruturas de dados Lock-Free também reduz o risco de deadlock.

O que é uma Condition no Lock?

Condition é o análogo de wait/notify para Lock, permitindo múltiplas filas de espera independentes. Cada chamada newCondition() cria uma fila separada, fornecendo um controle mais preciso sobre a ativação de threads em comparação com a fila única do synchronized.

Qual Lock escolher para uma aplicação móvel?

Para Android com corrotinas, use Mutex do kotlinx.coroutines — ele suspende a corrotina em vez de bloquear a thread. Para iOS com Swift 5.7+, os atores são preferíveis, pois sincronizam automaticamente o acesso ao estado. Reserve ReentrantLock para código legado e cenários de baixo nível.

Resumo

  • Lock é uma interface de gerenciamento explícito de bloqueios do java.util.concurrent.locks.
  • ReentrantLock é a implementação principal com suporte a reentrância e justiça.
  • ReadWriteLock separa bloqueios de leitura e escrita para cenários read-heavy.
  • StampedLock adiciona leitura otimista para máximo desempenho.
  • Timeouts e Conditions são as principais vantagens do Lock sobre o synchronized.
  • Deadlock é prevenido com ordem de aquisição consistente e uso de tryLock.
  • No desenvolvimento móvel, são recomendados corrotinas (Android) e atores (iOS).

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também