Sobes.tech
Middle

Por que ocorrem condições de corrida de threads e quais métodos existem para combatê-las?

sobes.tech IA

Resposta da IA

Condições de corrida (race conditions) ocorrem quando vários threads acedem simultaneamente a dados modificáveis partilhados, e o resultado depende da ordem de execução. Isto acontece porque o sistema operativo pode alternar entre threads a qualquer momento, interrompendo a sua execução.

Formas de combater:

  • Sincronização: Uso de primitivas de sincronização para garantir acesso exclusivo a recursos partilhados.

    • Palavra-chave synchronized: Aplicada a métodos ou blocos de código. Bloqueia o acesso a um objeto ou classe para outros threads até que o thread atual termine de executar o bloco/método sincronizado.

      // Sincronização de método
      public synchronized void updateData(int newValue) {
          // ... atualização de dados partilhados
      }
      
      // Sincronização de bloco de código
      public void processData() {
          synchronized (this) { // Ou qualquer outro objeto monitor
              // ... acesso a dados partilhados
          }
      }
      
    • Locks (Lock): Oferecem um controlo mais flexível sobre a sincronização em comparação com synchronized.

      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      private final Lock dataLock = new ReentrantLock();
      
      public void safeUpdateData(int newValue) {
          dataLock.lock(); // Aquisição do lock
          try {
              // ... atualização de dados partilhados
          } finally {
              dataLock.unlock(); // Liberação do lock
          }
      }
      
  • Variáveis atómicas (Atomic...): Proporcionam operações seguras para threads sobre tipos primitivos sem bloqueio explícito. Usadas para operações simples (incremento, comparação e troca).

    import java.util.concurrent.atomic.AtomicInteger;
    
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public void incrementCounter() {
        counter.incrementAndGet(); // Incremento atómico
    }
    
  • Coleções seguras para threads: Uso de coleções especiais do pacote java.util.concurrent (por exemplo, ConcurrentHashMap, CopyOnWriteArrayList), projetadas para acesso concorrente.

    import java.util.concurrent.ConcurrentHashMap;
    import java.util.Map;
    
    private final Map<String, Integer> safeMap = new ConcurrentHashMap<>();
    
  • Objetos imutáveis (Immutable): Se os dados não mudam após a criação, podem ser acessados com segurança por múltiplos threads sem sincronização.

  • Variáveis locais de thread (ThreadLocal): Cada thread recebe uma cópia da variável, evitando competição por acesso.

    private final ThreadLocal<Integer> threadId = new ThreadLocal<>();
    
    public void setThreadId() {
        threadId.set(Thread.currentThread().getId());
    }
    
    public int getThreadId() {
        return threadId.get();
    }
    
  • Design arquitetónico correto: Minimizar dados modificáveis partilhados e usar abordagens assíncronas ou transmissão de mensagens entre threads pode reduzir a probabilidade de condições de corrida.