Em nosso artigo anterior, exploramos a evolução histórica da Programação Orientada a Objetos e tocamos nos desafios que ela enfrenta em ambientes concorrentes. Hoje, mergulharemos profundamente nas dores de cabeça específicas que emergem quando múltiplas threads interagem com estado mutável compartilhado – problemas que têm atormentado desenvolvedores por décadas.
O Problema Fundamental: Estado Mutável Compartilhado
Imagine Arthur e Maria ambos tentando comprar o último ingresso disponível para um concerto através de uma aplicação web. Ambos clicam em "Comprar Agora" exatamente ao mesmo tempo. O que acontece a seguir ilustra os desafios centrais da programação concorrente:
Sem sincronização adequada, tanto Arthur quanto Maria podem ver availableTickets > 0, levando ambos a "com sucesso" comprarem o mesmo ingresso.

Os Quatro Cavaleiros da Programação Concorrente
Quando múltiplas threads interagem com estado compartilhado, quatro problemas primários podem surgir, cada um mais insidioso que o anterior.
Diagrama de estado mostrando os quatro modos de falha primários de concorrência e como eles levam a problemas do sistema. O Actor Model elimina essas questões através de passagem de mensagens e processamento sequencial dentro de actors.
1. Race Conditions
Race conditions ocorrem quando o resultado de um programa depende do timing e intercalação de threads. O exemplo do ingresso acima é uma race condition clássica.
O resultado? O sistema pensa que vendeu dois ingressos quando apenas um estava disponível, levando a corrupção de dados e clientes infelizes.
Este diagrama de sequência visualiza o problema exato de timing na race condition. Tanto Arthur quanto Maria verificam disponibilidade antes de qualquer um decrementar, resultando em ambos "com sucesso" comprarem o mesmo ingresso.

2. Deadlocks
Deadlocks acontecem quando duas ou mais threads ficam bloqueadas para sempre, esperando uma pela outra para liberar recursos:
Se a Thread 1 adquire lock1 enquanto a Thread 2 adquire lock2, elas esperarão para sempre uma pela outra.
Este diagrama de sequência mostra a dependência circular que cria um deadlock. A Thread 1 mantém lock1 e espera por lock2, enquanto a Thread 2 mantém lock2 e espera por lock1. Nenhuma pode prosseguir.

3. Livelocks
Livelocks são similares a deadlocks, mas as threads não estão bloqueadas – elas estão ativamente tentando resolver o conflito, criando um loop infinito de polidez:
As threads permanecem ativas mas não fazem progresso, como duas pessoas em um corredor ambas se movendo para esquerda e direita em sincronia.
Fluxo de livelock: threads detectam continuamente conflitos e cedem uma à outra, mas ambas tomam a mesma decisão simultaneamente, resultando em um loop infinito de cedência mútua sem progresso.

4. Starvation
Starvation ocorre quando uma thread é perpetuamente negada acesso a recursos que precisa:

Threads de baixa prioridade podem esperar indefinidamente enquanto threads de alta prioridade continuamente pegam recursos.
Soluções Tradicionais e Suas Limitações
Java fornece vários mecanismos para lidar com essas questões, mas cada um vem com trade-offs:
Palavras-chave Synchronized
Prós: Simples, previne race conditions Contras: Escalabilidade ruim, potencial para deadlocks
Campos Volatile
Prós: Garante visibilidade de mudanças entre threads Contras: Funciona apenas para operações únicas, não previne race conditions em operações complexas
Classes Atômicas
Prós: Melhor performance que synchronized Contras: Limitado a operações simples, não resolve coordenação complexa
O Dilema do Cache
Aplicações modernas frequentemente adicionam camadas de cache para performance, o que introduz complexidade adicional:
Agora temos que nos preocupar com invalidação de cache entre múltiplas instâncias, consistência entre cache e banco de dados, e locking distribuído em ambientes clusterizados.
Por Que OOP Luta com Concorrência
O princípio central da Programação Orientada a Objetos de encapsulamento assume que objetos podem proteger seu estado interno. Mas quando múltiplas threads acessam o mesmo objeto, essa proteção se quebra:
- Encapsulamento não é suficiente: Campos privados não protegem contra acesso concorrente
- Sincronização em nível de método é muito grosseira: Cria gargalos desnecessários
- Grafos de objetos complexos requerem locking complexo: Levando a riscos de deadlock
- Herança complica thread safety: Subclasses podem quebrar suposições da classe pai
O Problema do Modelo Mental
Talvez o maior desafio seja que estado mutável compartilhado requer que desenvolvedores pensem sobre todas as possíveis intercalações de execução de threads. Isso rapidamente se torna mentalmente esmagador:
- Com 2 threads e 3 operações cada, há 20 ordens de execução possíveis
- Com 3 threads e 4 operações cada, há 369.600 ordens de execução possíveis
- Com aplicações realistas tendo centenas de threads... a complexidade explode
Um Caminho Diferente à Frente
O Actor Model aborda esses problemas eliminando estado mutável compartilhado completamente. Ao invés de múltiplas threads acessando os mesmos dados, cada actor possui seu estado completamente e a comunicação acontece apenas através de mensagens. Isso elimina locks, race conditions e deadlocks inteiramente, enquanto fornece isolamento de falhas natural e supervisão.
Considere como nosso serviço de ingressos poderia parecer com actors:
Sem palavras-chave de sincronização, sem locks, sem race conditions – apenas processamento simples e sequencial de mensagens.
Olhando para Frente
Os problemas que exploramos hoje – race conditions, deadlocks, livelocks e starvation – têm atormentado programação concorrente por décadas. Soluções tradicionais de OOP, embora funcionais, frequentemente criam mais complexidade do que resolvem.
Em nosso próximo artigo, exploraremos como o Actor Model fornece soluções elegantes para esses desafios e mergulharemos em padrões de implementação prática usando Akka e Apache Pekko na JVM.
A jornada do estado mutável compartilhado para arquiteturas de passagem de mensagens não é apenas sobre evitar bugs – é sobre construir sistemas que são inerentemente mais escaláveis, manuteníveis e resilientes a falhas.
Próximo na Parte 3: Implementaremos um sistema completo baseado em Actor, exploraremos estratégias de supervisão e veremos como passagem de mensagens elimina as armadilhas de concorrência que discutimos hoje.
