Depois de abordar os principais riscos da execução de programas paralelos (como
atomicidade ou
visibilidade ), veremos alguns designs de classes que nos ajudarão a evitar as armadilhas mencionadas acima. Algumas dessas construções criam objetos thread-safe, permitindo-nos compartilhá-los com segurança entre threads. Como exemplo, veremos objetos imutáveis e sem estado. Outras visualizações impedirão que diferentes threads modifiquem dados, como variáveis locais de thread.
Você pode visualizar todos os códigos-fonte no
Github .
1. Objetos Imutáveis Objetos imutáveis possuem estado (possuem dados que representam o estado do objeto), mas é definido no momento da criação no construtor, uma vez que uma instância do objeto foi criada e o estado não pode ser alterado. Embora os threads possam alternar, um objeto ainda possui um estado possível. Como todos os campos são somente leitura, nenhum thread pode alterar os dados do objeto. Por causa disso, um fluxo imutável é inerentemente seguro para threads. A classe Product demonstra uma classe imutável. Ele preenche todos os seus campos no construtor e nenhum deles muda:
public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } }
Em alguns casos, tornar os campos definitivos não será suficiente. Por exemplo, a classe
MutableProduct não é imutável, embora todos os campos sejam finais: Por que a classe acima não é imutável? A razão é que permitimos que uma referência seja recuperada de uma classe. O campo '
categorias ' é uma referência mutável, portanto, uma vez recebido, o cliente pode modificá-lo. Para ilustrar isso, considere o seguinte programa: E a saída do console: Como o campo 'categorias' é mutável e foi obtido de um objeto, o cliente modificou esta lista. Um objeto que deveria ser imutável foi alterado, resultando em um novo estado. Se você deseja representar o conteúdo de uma lista, você pode usar uma representação de lista imutável:
2. Objetos sem Estado Os objetos sem estado são semelhantes aos objetos imutáveis, mas neste caso eles não possuem um estado, nem mesmo um. Quando um objeto é sem estado, ele não precisa persistir nenhum dado entre as chamadas. Como não existe nenhum estado, nenhum thread pode afetar o resultado de outro thread chamando os métodos do objeto. Por esse motivo, objetos sem estado são inerentemente seguros para threads. A classe
ProductHandler é um exemplo desse tipo de objeto. Ele contém diversas operações em objetos Product e não armazena nenhum dado entre chamadas. O resultado da operação é independente de chamadas anteriores ou de quaisquer dados armazenados: No método sumCart, ProductHandler converte a lista de produtos em uma matriz para uso em um loop for-each para iterar por todos os elementos. A lista de iteradores não é thread-safe e pode gerar uma
ConcurrentModificationException se houver alterações durante a iteração. Dependendo de suas necessidades, você pode escolher uma
estratégia diferente .
3. Variáveis locais de thread Variáveis locais de thread são aquelas variáveis definidas dentro de um thread. Nenhum outro tópico os verá e não os alterará. O primeiro tipo são variáveis locais. No exemplo abaixo, a variável
total é armazenada na pilha do thread:
public final class MutableProduct { private final String id; private final String name; private final double price; private final List
categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List
getCategories() { return this.categories; } public List
getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } }
public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); }
Product categories A B C Modified Product categories B C
public List
getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); }
public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List
cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } }
public double sumCart(List
cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; }
Apenas tenha em mente que se você definir uma referência em vez de uma variável primitiva e retorná-la, ela sairá de seus limites. Talvez você não saiba onde o link retornado foi criado. O código que chama o método
sumCart poderia armazená-lo em um campo estático e permitir que ele fosse acessado por diferentes threads. O segundo tipo é a classe
ThreadLocal . Esta classe fornece armazenamento independente para cada thread. Os valores armazenados em ThreadLocal estão disponíveis para qualquer código no mesmo thread. A classe ClientRequestId mostra um exemplo do uso da classe ThreadLocale:
A classe ProductHandlerThreadLocal usa o ClientRequestId para retornar o mesmo ID gerado no mesmo thread: Ao executar o método principal, a saída do console mostrará IDs diferentes para cada thread. Por exemplo: Se você for usar ThreadLocale, deverá cuidar de alguns riscos de uso ao mesclar threads (como em aplicativos de servidor). Você pode ter vazamentos de memória ou de informações entre solicitações. Não vou entrar em muitos detalhes sobre isso porque... O artigo “
Como dar um tiro no pé com ThreadLocale ” demonstra bem como isso pode acontecer.
4. Usando sincronização Outra maneira de fornecer acesso seguro a objetos é por meio da sincronização. Se sincronizarmos todos os acessos a uma referência, apenas um objeto thread irá acessá-la em um determinado momento. Discutiremos isso em postagens futuras.
5. Conclusão Vimos vários métodos que permitem construir objetos simples que podem ser acessados por múltiplas threads. É muito mais difícil evitar erros multithread se um objeto puder ter vários estados. Por outro lado, se um objeto pode ter apenas um estado ou nenhum, não precisamos nos preocupar com vários threads acessando o objeto ao mesmo tempo. O original
está aqui .
public class ClientRequestId { private static final ThreadLocal
id = new ThreadLocal
() { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } }
public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } }
T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
GO TO FULL VERSION