JavaRush /Java-Blog /Random-DE /Parallelität in Java. Tutorial – Thread-sichere Konstrukt...
0xFF
Level 9
Донецк

Parallelität in Java. Tutorial – Thread-sichere Konstrukte.

Veröffentlicht in der Gruppe Random-DE
Nachdem wir die Hauptrisiken der Ausführung paralleler Programme (wie Atomizität oder Sichtbarkeit ) behandelt haben, schauen wir uns einige Klassendesigns an, die uns helfen, die oben genannten Fallstricke zu vermeiden. Einige dieser Konstrukte erstellen threadsichere Objekte, sodass wir sie sicher zwischen Threads teilen können. Als Beispiel betrachten wir unveränderliche und zustandslose Objekte. Andere Ansichten verhindern, dass verschiedene Threads Daten ändern, z. B. lokale Thread-Variablen. Sie können alle Quellcodes auf Github ansehen . 1. Unveränderliche Objekte Unveränderliche Objekte haben einen Status (haben Daten, die den Status des Objekts darstellen), aber dieser wird zum Zeitpunkt der Erstellung im Konstruktor festgelegt, sobald eine Instanz des Objekts erstellt wurde und der Status nicht geändert werden kann. Obwohl sich Threads abwechseln können, hat ein Objekt immer noch einen möglichen Zustand. Da alle Felder schreibgeschützt sind, kann kein Thread die Daten des Objekts ändern. Aus diesem Grund ist ein unveränderlicher Stream von Natur aus threadsicher. Die Produktklasse demonstriert eine unveränderliche Klasse. Es füllt alle seine Felder im Konstruktor aus und keines davon ändert sich: 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; } } In manchen Fällen reicht es nicht aus, die Felder endgültig zu machen. Beispielsweise ist die MutableProduct- Klasse nicht unveränderlich, obwohl alle Felder endgültig sind: Warum ist die obige Klasse nicht unveränderlich? Der Grund dafür ist, dass wir das Abrufen einer Referenz aus einer Klasse zulassen. Das Feld „ Kategorien “ ist eine veränderbare Referenz, sodass der Client es nach Erhalt ändern kann. Betrachten Sie zur Veranschaulichung das folgende Programm: Und die Konsolenausgabe: Da das Feld „Kategorien“ veränderbar ist und von einem Objekt abgerufen wurde, hat der Client diese Liste geändert. Ein Objekt, das unveränderlich sein sollte, wurde geändert, was zu einem neuen Zustand führt. Wenn Sie den Inhalt einer Liste darstellen möchten, können Sie eine unveränderliche Listendarstellung verwenden: 2. Zustandslose Objekte Zustandslose Objekte ähneln unveränderlichen Objekten, haben aber in diesem Fall keinen Zustand, nicht einmal einen. Wenn es sich bei einem Objekt um ein zustandsloses Objekt handelt, müssen keine Daten zwischen Aufrufen gespeichert werden. Da kein Status existiert, kann kein Thread das Ergebnis eines anderen Threads beeinflussen, indem er die Methoden des Objekts aufruft. Aus diesem Grund sind zustandslose Objekte von Natur aus Thread-sicher. Die ProductHandler- Klasse ist ein Beispiel für diesen Objekttyp. Es enthält mehrere Vorgänge für Produktobjekte und speichert keine Daten zwischen Aufrufen. Das Ergebnis der Operation ist unabhängig von vorherigen Aufrufen oder gespeicherten Daten: In der sumCart-Methode konvertiert ProductHandler die Produktliste in ein Array zur Verwendung in einer for-each-Schleife, um alle Elemente zu durchlaufen. Die Iteratorliste ist nicht threadsicher und löst möglicherweise eine ConcurrentModificationException aus , wenn während der Iteration Änderungen vorgenommen werden. Abhängig von Ihren Bedürfnissen können Sie eine andere Strategie wählen . 3. Thread-Lokale Variablen Thread-Lokale Variablen sind jene Variablen, die innerhalb eines Threads definiert sind. Kein anderer Thread sieht sie und wird sie auch nicht ändern. Der erste Typ sind lokale Variablen. Im folgenden Beispiel wird die Variable „ total“ auf dem Stapel des Threads gespeichert: 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; } Bedenken Sie jedoch, dass, wenn Sie eine Referenz anstelle einer primitiven Variablen definieren und diese zurückgeben, diese ihre Grenzen verlässt. Möglicherweise wissen Sie nicht, wo der zurückgegebene Link erstellt wurde. Der Code, der die sumCart- Methode aufruft , könnte sie in einem statischen Feld speichern und den Zugriff durch verschiedene Threads ermöglichen. Der zweite Typ ist die ThreadLocal- Klasse . Diese Klasse stellt unabhängigen Speicher für jeden Thread bereit. In ThreadLocal gespeicherte Werte stehen jedem Code im selben Thread zur Verfügung. Die ClientRequestId-Klasse zeigt ein Beispiel für die Verwendung der ThreadLocale-Klasse: Die ProductHandlerThreadLocal- Klasse verwendet die ClientRequestId, um dieselbe generierte ID im selben Thread zurückzugeben: Beim Ausführen der Hauptmethode zeigt die Konsolenausgabe unterschiedliche IDs für jeden Thread an. Beispiel: Wenn Sie ThreadLocale verwenden möchten, müssen Sie beim Zusammenführen von Threads (wie in Serveranwendungen) einige Nutzungsrisiken berücksichtigen. Zwischen Anfragen kann es zu Speicherverlusten oder Informationsverlusten kommen. Ich werde hierauf nicht zu sehr ins Detail gehen, weil... Der Artikel „ Wie man sich mit ThreadLocale selbst ins Bein schießt “ zeigt gut, wie das passieren kann. 4. Verwendung der Synchronisierung Eine weitere Möglichkeit, threadsicheren Zugriff auf Objekte bereitzustellen, ist die Synchronisierung. Wenn wir alle Zugriffe auf eine Referenz synchronisieren, greift zu einem bestimmten Zeitpunkt nur ein Thread-Objekt darauf zu. Wir werden dies in zukünftigen Beiträgen besprechen. 5. Fazit Wir haben uns mehrere Methoden angesehen, mit denen Sie einfache Objekte erstellen können, auf die mehrere Threads zugreifen können. Es ist viel schwieriger, Multithread-Fehler zu verhindern, wenn ein Objekt mehrere Zustände haben kann. Wenn ein Objekt andererseits nur einen oder keinen Status haben kann, müssen wir uns keine Sorgen machen, dass mehrere Threads gleichzeitig auf das Objekt zugreifen. Das Original gibt es hier . 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 ...
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION