Fundamentos8 min

Sincronismo e Contenção: quando threads brigam por recursos

Contenção é uma das causas mais sutis de problemas de performance. Entenda como identificar e resolver disputas por recursos.

Em sistemas concorrentes, múltiplas threads ou processos frequentemente precisam acessar os mesmos recursos. Quando isso acontece de forma descontrolada, surge a contenção — uma disputa que pode devastar a performance do sistema.

Este artigo explora o que é contenção, como ela se manifesta, e estratégias para minimizá-la.

Contenção é o preço da concorrência mal gerenciada.

O que é Contenção

Contenção ocorre quando múltiplos processos ou threads competem por um recurso limitado que só pode ser usado por um de cada vez.

Exemplos de recursos disputados

  • Locks/mutexes: apenas uma thread pode segurar por vez
  • Conexões de banco: pool limitado
  • CPU cores: mais threads que cores
  • I/O de disco: uma operação por vez no mesmo arquivo
  • Rede: bandwidth limitado

O custo da contenção

Quando há contenção:

  1. Threads ficam esperando em vez de trabalhando
  2. CPU gasta tempo em context switching
  3. Throughput cai mesmo com recursos disponíveis
  4. Latência aumenta de forma imprevisível

Tipos de Contenção

Contenção de Lock

A mais comum em código.

synchronized (this) {
    // Apenas uma thread por vez
    processRequest();
}

Se processRequest() demora 10ms e chegam 100 requisições/segundo:

  • 100 × 10ms = 1000ms de trabalho por segundo
  • Apenas 1 thread pode executar = gargalo severo

Contenção de CPU

Mais threads ativas que cores disponíveis.

8 cores, 100 threads ativas
= Muito context switching
= Overhead significativo

Contenção de I/O

Múltiplos processos tentando ler/escrever simultaneamente.

Thread 1: write(file, data1)
Thread 2: write(file, data2)  // Espera thread 1
Thread 3: write(file, data3)  // Espera thread 2

Contenção de conexão

Pool de conexões esgotado.

Pool: 10 conexões
Requisições simultâneas: 50
= 40 requisições esperando

Identificando Contenção

Sintomas

  1. CPU baixa, latência alta: threads esperando, não trabalhando
  2. Throughput não escala: mais threads não significa mais trabalho
  3. Latência errática: depende de quem chegou primeiro
  4. Lock contention metrics: ferramentas de profiling mostram espera

Ferramentas de diagnóstico

Java:

  • jstack para ver threads em BLOCKED
  • JFR (Java Flight Recorder) para lock contention
  • VisualVM

Linux:

  • perf para CPU profiling
  • strace para I/O
  • /proc/[pid]/status para context switches

Métricas a monitorar:

  • Threads em estado BLOCKED/WAITING
  • Context switches por segundo
  • Tempo médio de espera por lock

Estratégias de Mitigação

1. Reduza o escopo do lock

Antes:

synchronized (this) {
    validateInput(data);
    processData(data);
    saveToDatabase(data);
    sendNotification(data);
}

Depois:

validateInput(data);  // Sem lock
Data processed = processData(data);  // Sem lock

synchronized (this) {
    saveToDatabase(processed);  // Apenas o necessário
}

sendNotification(data);  // Sem lock

2. Use estruturas lock-free

// Em vez de
synchronized (counter) {
    counter++;
}

// Use
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

3. Particione recursos

Em vez de um lock global, use locks por partição.

// Em vez de
synchronized (cache) {
    cache.put(key, value);
}

// Use locks por segmento
int segment = key.hashCode() % NUM_SEGMENTS;
synchronized (locks[segment]) {
    segments[segment].put(key, value);
}

ConcurrentHashMap faz exatamente isso.

4. Aumente pools

Se conexões são o gargalo, aumente o pool — mas com cuidado:

  • Cada conexão consome memória
  • Backend precisa suportar mais conexões

5. Use operações assíncronas

// Em vez de esperar
CompletableFuture<Result> future = processAsync(data);
// Continue fazendo outras coisas
future.thenAccept(result -> handleResult(result));

6. Batch operations

// Em vez de
for (Item item : items) {
    synchronized (lock) {
        save(item);
    }
}

// Faça batch
synchronized (lock) {
    saveAll(items);
}

Contenção em Banco de Dados

Row-level locking

-- Transação 1
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Transação 2 (espera transação 1)
UPDATE accounts SET balance = balance + 100 WHERE id = 1;

Mitigações:

  • Transações curtas
  • Ordenar acessos consistentemente
  • Usar níveis de isolamento adequados

Table-level locking

Algumas operações bloqueiam tabelas inteiras:

  • ALTER TABLE
  • LOCK TABLE
  • Índices sendo criados

Deadlocks

Quando duas transações esperam uma pela outra:

T1: lock(A), espera lock(B)
T2: lock(B), espera lock(A)
= Deadlock

Prevenção:

  • Sempre adquirir locks na mesma ordem
  • Timeout em transações
  • Detectar e resolver automaticamente

Lei de Amdahl e Contenção

A Lei de Amdahl mostra que a parte sequencial limita o ganho de paralelização:

Speedup máximo = 1 / (S + (1-S)/N)

S = fração sequencial (contenção)
N = número de processadores

Se 10% do código é sequencial (contenção):

  • Com 10 cores: speedup máximo = 5.3x
  • Com 100 cores: speedup máximo = 9.2x
  • Com 1000 cores: speedup máximo = 9.9x

Conclusão: reduzir contenção tem mais impacto que adicionar recursos.

Boas Práticas

  1. Meça antes de otimizar: nem toda contenção é problema
  2. Minimize seções críticas: faça o mínimo dentro de locks
  3. Prefira estruturas concorrentes: ConcurrentHashMap, AtomicInteger
  4. Evite locks aninhados: fonte de deadlocks
  5. Use timeouts: não espere infinitamente
  6. Monitore continuamente: contenção pode surgir com escala

Conclusão

Contenção é um dos gargalos mais sutis e impactantes em sistemas concorrentes. Ela:

  • Limita escalabilidade
  • Aumenta latência
  • Desperdiça recursos

Para combatê-la:

  1. Identifique onde ocorre (profiling)
  2. Reduza seções críticas
  3. Particione recursos quando possível
  4. Use estruturas adequadas para concorrência

Paralelismo sem contenção é velocidade. Paralelismo com contenção é desperdício.

contençãoconcorrêncialocksthreads
Compartilhar:
Read in English

Quer entender os limites da sua plataforma?

Entre em contato para uma avaliação de performance.

Fale Conosco