JavaRush /Blog Java /Random-FR /Concurrence en Java. Tutoriel - Constructions thread-safe...
0xFF
Niveau 9
Донецк

Concurrence en Java. Tutoriel - Constructions thread-safe.

Publié dans le groupe Random-FR
Après avoir abordé les principaux risques liés à l'exécution de programmes parallèles (tels que l'atomicité ou la visibilité ), nous examinerons quelques conceptions de classes qui nous aideront à éviter les pièges mentionnés ci-dessus. Certaines de ces constructions créent des objets thread-safe, nous permettant de les partager en toute sécurité entre les threads. A titre d'exemple, nous examinerons les objets immuables et apatrides. D'autres vues empêcheront différents threads de modifier les données, telles que les variables locales du thread. Vous pouvez afficher tous les codes sources sur Github . 1. Objets immuables Les objets immuables ont un état (ont des données qui représentent l'état de l'objet), mais il est défini au moment de la création dans le constructeur, une fois qu'une instance de l'objet a été créée et que l'état ne peut pas être modifié. Bien que les threads puissent alterner, un objet a toujours un état possible. Puisque tous les champs sont en lecture seule, aucun thread ne peut modifier les données de l'objet. Pour cette raison, un flux immuable est intrinsèquement thread-safe. La classe Product démontre une classe immuable. Il remplit tous ses champs dans le constructeur et aucun d'entre eux ne change : 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; } } dans certains cas, rendre les champs définitifs ne suffira pas. Par exemple, la classe MutableProduct n'est pas immuable, bien que tous les champs soient finaux : Pourquoi la classe ci-dessus n'est-elle pas immuable ? La raison est que nous autorisons la récupération d’une référence à partir d’une classe. Le champ ' categories ' est une référence mutable, donc une fois reçu, le client peut le modifier. Pour illustrer cela, considérons le programme suivant : Et la sortie de la console : Puisque le champ 'categories' est mutable et a été obtenu à partir d'un objet, le client a modifié cette liste. Un objet qui devrait être immuable a été modifié, entraînant un nouvel état. Si vous souhaitez représenter le contenu d'une liste, vous pouvez utiliser une représentation de liste immuable : 2. Objets sans état Les objets sans état sont similaires aux objets immuables, mais dans ce cas, ils n'ont pas d'état, pas même un. Lorsqu'un objet est un objet sans état, il n'est pas nécessaire qu'il conserve des données entre les appels. Puisqu’aucun état n’existe, aucun thread ne peut affecter le résultat d’un autre thread en appelant les méthodes de l’objet. Pour cette raison, les objets sans état sont intrinsèquement thread-safe. La classe ProductHandler est un exemple de ce type d'objet. Il contient plusieurs opérations sur les objets Product et ne stocke aucune donnée entre les appels. Le résultat de l'opération est indépendant des appels précédents ou des données stockées : dans la méthode sumCart, ProductHandler convertit la liste Product en un tableau à utiliser dans une boucle for-each pour parcourir tous les éléments. La liste des itérateurs n'est pas thread-safe et peut lever une ConcurrentModificationException s'il y a des changements au cours de l'itération. En fonction de vos besoins, vous pouvez choisir une stratégie différente . 3. Variables locales de thread Les variables locales de thread sont les variables définies dans un thread. Aucun autre fil de discussion ne les voit et ne les modifiera. Le premier type est celui des variables locales. Dans l'exemple ci-dessous, la variable total est stockée sur la pile du 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; } Gardez simplement à l’esprit que si vous définissez une référence au lieu d’une variable primitive et que vous la renvoyez, elle quittera ses limites. Vous ne savez peut-être pas où le lien renvoyé a été créé. Le code qui appelle la méthode sumCart pourrait la stocker dans un champ statique et permettre à différents threads d'y accéder. Le deuxième type est la classe ThreadLocal . Cette classe fournit un stockage indépendant pour chaque thread. Les valeurs stockées dans ThreadLocal sont disponibles pour n'importe quel code sur le même thread. La classe ClientRequestId montre un exemple d'utilisation de la classe ThreadLocale : La classe ProductHandlerThreadLocal utilise ClientRequestId pour renvoyer le même ID généré sur le même thread : lors de l'exécution de la méthode principale, la sortie de la console affichera des ID différents pour chaque thread. Par exemple : si vous envisagez d'utiliser ThreadLocale, vous devez prendre en compte certains risques d'utilisation lors de la fusion de threads (comme dans les applications serveur). Vous pouvez avoir des fuites de mémoire ou des fuites d’informations entre les requêtes. Je n'entrerai pas dans les détails car... L'article « Comment se tirer une balle dans le pied avec ThreadLocale » montre bien comment cela peut arriver. 4. Utilisation de la synchronisation Une autre façon de fournir un accès thread-safe aux objets consiste à utiliser la synchronisation. Si nous synchronisons tous les accès à une référence, alors un seul objet thread y accédera à un instant donné. Nous en discuterons dans les prochains articles. 5. Conclusion Nous avons examiné plusieurs méthodes qui vous permettent de créer des objets simples accessibles par plusieurs threads. Il est beaucoup plus difficile d'éviter les erreurs multithread si un objet peut avoir plusieurs états. D'un autre côté, si un objet ne peut avoir qu'un seul état ou aucun, nous n'avons pas à nous soucier de l'accès de plusieurs threads à l'objet en même temps. L'original est ici . 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 ...
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION