DEV Community

Java Efetivo (livro)
Java Efetivo (livro)

Posted on

Cap 11 Concorrência Item 78: Sincronize o acesso aos dados mutáveis compartilhados

As threads permitem que inúmeras atividades ocorram simultaneamente.
A programação concorrente é mais difícil do que a programação em uma única thread, pois muitas coisas podem dar errado, e pode ser difícil de reproduzir as falhas. Você não pode evitar a concorrência. Ela é inerente à plataforma e um requisito para se conseguir um bom desempenho dos processadores multicore, que agora estão por toda parte. Este capítulo apresenta conselhos para ajudá-lo a escrever programas concorrentes claros, corretos e bem documentados.

1. Importância da sincronização para dados mutáveis compartilhados

  • A sincronização com a palavra-chave synchronized garante:
  • Exclusão mútua: Apenas uma thread pode executar um bloco sincronizado por vez.
  • Visibilidade: As mudanças feitas por uma thread tornam-se visíveis para outras threads.

2. Problemas sem sincronização

  • Sem sincronização, alterações podem não ser visíveis para outras threads.
  • Exemplo: Parar uma thread com um campo boolean (sem sincronização)
public class StopThread {
    private static boolean stopRequested = false;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            while (!stopRequested) {
                // Loop infinito, pois mudanças em stopRequested podem não ser visíveis
            }
        });
        backgroundThread.start();

        Thread.sleep(1000);
        stopRequested = true; // Pode nunca ser visto pela outra thread
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Correção com sincronização

  • Sincronizar leitura e escrita do campo para garantir visibilidade:
public class StopThread {
    private static boolean stopRequested;

    public static synchronized void requestStop() {
        stopRequested = true;
    }

    public static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            while (!stopRequested()) {
                // Loop agora termina corretamente
            }
        });
        backgroundThread.start();

        Thread.sleep(1000);
        requestStop();
    }
}

Enter fullscreen mode Exit fullscreen mode

4. Uso de volatile para comunicação entre threads

  • O modificador volatile garante visibilidade sem exclusão mútua:
public class StopThread {
    private static volatile boolean stopRequested = false;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            while (!stopRequested) {
                // Loop agora termina corretamente com volatile
            }
        });
        backgroundThread.start();

        Thread.sleep(1000);
        stopRequested = true;
    }
}

Enter fullscreen mode Exit fullscreen mode

5. Problemas com operações compostas (exemplo: incremento não atômico)

  • O operador ++ não é seguro para threads, pois envolve múltiplas operações:
public class SerialNumberGenerator {
    private static volatile int nextSerialNumber = 0;

    public static int generateSerialNumber() {
        return nextSerialNumber++; // Pode gerar números repetidos
    }
}

Enter fullscreen mode Exit fullscreen mode

Correção com sincronização:

public class SerialNumberGenerator {
    private static int nextSerialNumber = 0;

    public static synchronized int generateSerialNumber() {
        return nextSerialNumber++;
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Melhoria com AtomicLong para desempenho e segurança:
import java.util.concurrent.atomic.AtomicLong;

public class SerialNumberGenerator {
    private static final AtomicLong nextSerialNumber = new AtomicLong();

    public static long generateSerialNumber() {
        return nextSerialNumber.getAndIncrement();
    }
}

Enter fullscreen mode Exit fullscreen mode

6. Princípios gerais
Evitar compartilhamento de dados mutáveis:

  • Preferir dados imutáveis sempre que possível.
  • Confinar dados mutáveis a uma única thread.

Publicação segura:
Garantir que um objeto compartilhado seja visível para outras threads:

  • Utilizando campos final, volatile, ou sincronização explícita.
  • Usando coleções concorrentes, como ConcurrentHashMap.

7. Resumo das boas práticas

  • Sempre sincronizar leitura e escrita de dados mutáveis compartilhados.
  • Usar volatile apenas para comunicação simples entre threads.
  • Preferir classes de utilitários seguros para threads, como as do pacote java.util.concurrent.

Com esses exemplos e práticas, é possível criar programas concorrentes que são claros, corretos e fáceis de manter.


Exemplos do livro:

Image description

Image description

Image description

Image description

Top comments (0)