JavaRush /Blogue Java /Random-PT /Simultaneidade em Java. Tutorial - Construções thread-saf...
0xFF
Nível 9
Донецк

Simultaneidade em Java. Tutorial - Construções thread-safe.

Publicado no grupo Random-PT
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 ...
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION