Código Thread-Safe

Aula
Estado
💡
Eliminar a dependência entre threads
  • Como cada thread tem sua própria porção da Stack, e ela é invisível para outras threads, valores armazenados na Stack são thread-safe
  • A memória Heap é compartilhada entre threads; por isso apenas objetos imutáveis são thread-safe, objetos mutáveis são thread-unsafe
    • objetos mutáveis podem ser alterados por uma thread sem que outra perceba, e essa acaba por utilizar um valor incorreto
    • com objetos mutáveis pode ocorrer do compilador utilizar um valor em cache não percebendo que o cache é inválido, devido a alterações feitas em outras threads
  • É possível desabilitar a optimização de cache local de um valor de Heap por uma thread, forçando sempre a leitura da memória principal; utiliza-se a palavra reservada volatile

Ações atômicas não bloqueantes

  • Uma ação atômica é uma ação que é garantida de ser executada em uma thread sem interrupção;
    • operações que a CPU consegue executar em um único ciclo são atómicas, por definição
    • atribuição de variáveis são ações atômicas, exceto para os tipos long e double — são valores de 64 bits que levariam duas operações em uma plataforma 32 bits
    • operações como + - / * % ++ — não são atômicas
  • O pacote java.util.concurrent.atomic dispõe classes que implementam comportamentos atômicos em variáveis únicas de forma thread-safe e prevenindo lock's.
    • algumas delas são AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference<V>
    • as classes desse pacote fazem as alterações não no objeto da memória principal, mas no cópia do objeto que está em cache; isso permite que, ao atualizar a memória principal, a operação pareça atômica
    • variáveis atômicas também se comportam como variáveis volatile

Travas intrínsecas

💡
Bloqueia acesso a um objeto compartilhado, por outros objetos
  • São feitos através de blocos de código synchornized
  • Garante a ordem de execução e um objeto consistente
  • Lógica síncrona pode causar um gargalo de performance e escalabilidade, em aplicações multi-thread
  • A classe Collections dispõe wrappers síncronos para objetos de coleção como Collection, List, Set e Map; com eles a manipulação de itens (através de métodos da própria instância) a coleção não precisa ser feita em um bloco synchronized
    • a leitura de uma lista síncrona deve ser feita dentro de um bloco synchronized, caso contrário pode ocorrer de uma thread lê-la enquanto outra thread adiciona um item, podendo resultar em inconsistência nos dados
notion image

Automação de concorrência não-bloqueante

Através de wrappers de coleções como CopyOnWriteArrayList e CopyOnWriteArraySet, é possível utilizar listas de forma segura sem a necessidade de um bloco síncrono. Isso ocorrer pois sempre que uma operação de mutação é feita sobre esses wrappers, eles criam uma cópia da coleção e aplicam a alteração nessa cópia.
🚨
Por ser uma operação muito custosa, é uma abordagem utilizada apenas em coleções pequenas em que sejam feitas poucas operações de mutação
notion image

Mecanismos alternativos para travas

  • O pacote java.util.concurrent.locks dispõe diversas opções para abordar a lógica de travas
Exemplo utilizando as travas de leitura e escrita da classe ReentrantReadWriteLock para implementar um bloco de código síncrono
Exemplo utilizando as travas de leitura e escrita da classe ReentrantReadWriteLock para implementar um bloco de código síncrono