JavaRush /Java Blog /Random-IT /Concorrenza in Java. Tutorial: costrutti thread-safe.
0xFF
Livello 9
Донецк

Concorrenza in Java. Tutorial: costrutti thread-safe.

Pubblicato nel gruppo Random-IT
Dopo aver trattato i principali rischi legati all'esecuzione di programmi paralleli (come atomicità o visibilità ), esamineremo alcuni progetti di classi che ci aiuteranno a prevenire le trappole sopra menzionate. Alcuni di questi costrutti creano oggetti thread-safe, permettendoci di condividerli in modo sicuro tra thread. Ad esempio, esamineremo oggetti immutabili e senza stato. Altre visualizzazioni impediranno a thread diversi di modificare i dati, come le variabili locali del thread. Puoi visualizzare tutti i codici sorgente su Github . 1. Oggetti immutabili Gli oggetti immutabili hanno uno stato (hanno dati che rappresentano lo stato dell'oggetto), ma viene impostato al momento della creazione nel costruttore, una volta creata un'istanza dell'oggetto e lo stato non può essere modificato. Sebbene i thread possano alternarsi, un oggetto ha ancora uno stato possibile. Poiché tutti i campi sono di sola lettura, nessun thread può modificare i dati dell'oggetto. Per questo motivo, un flusso immutabile è intrinsecamente thread-safe. La classe Product dimostra una classe immutabile. Compila tutti i campi nel costruttore e nessuno di essi cambia: 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 alcuni casi, rendere i campi definitivi non sarà sufficiente. Ad esempio, la classe MutableProduct non è immutabile, sebbene tutti i campi siano definitivi: Perché la classe sopra non è immutabile? Il motivo è che consentiamo il recupero di un riferimento da una classe. Il campo ' categorie ' è un riferimento mutabile, quindi una volta ricevuto, il cliente può modificarlo. Per illustrare ciò, si consideri il seguente programma: E l'output della console: poiché il campo "categorie" è modificabile ed è stato ottenuto da un oggetto, il client ha modificato questo elenco. Un oggetto che dovrebbe essere immutabile è stato modificato, risultando in un nuovo stato. Se vuoi rappresentare il contenuto di una lista, puoi usare una rappresentazione di lista immutabile: 2. Oggetti senza stato Gli oggetti senza stato sono simili agli oggetti immutabili, ma in questo caso non hanno uno stato, nemmeno uno. Quando un oggetto è un oggetto senza stato, non è necessario che i dati persistano tra le chiamate. Poiché non esiste uno stato, nessun thread può influenzare il risultato di un altro thread chiamando i metodi dell'oggetto. Per questo motivo, gli oggetti senza stato sono intrinsecamente thread-safe. La classe ProductHandler è un esempio di questo tipo di oggetto. Contiene più operazioni sugli oggetti Product e non memorizza alcun dato tra le chiamate. Il risultato dell'operazione è indipendente dalle chiamate precedenti o dai dati memorizzati: nel metodo sumCart, ProductHandler converte l'elenco Product in un array da utilizzare in un ciclo for-each per scorrere tutti gli elementi. L'elenco degli iteratori non è thread-safe e potrebbe generare un'eccezione ConcurrentModificationException se vengono apportate modifiche durante l'iterazione. A seconda delle tue esigenze, puoi scegliere una strategia diversa . 3. Variabili locali del thread Le variabili locali del thread sono quelle variabili definite all'interno di un thread. Nessun altro thread li vede e non li modificherà. Il primo tipo sono le variabili locali. Nell'esempio seguente, la variabile total viene memorizzata nello stack del 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; } Tieni presente che se definisci un riferimento anziché una variabile primitiva e lo restituisci, lascerà i suoi limiti. Potresti non sapere dove è stato creato il collegamento restituito. Il codice che chiama il metodo sumCart potrebbe memorizzarlo in un campo statico e consentirne l'accesso da parte di thread diversi. Il secondo tipo è la classe ThreadLocal . Questa classe fornisce archiviazione indipendente per ogni thread. I valori archiviati in ThreadLocal sono disponibili per qualsiasi codice sullo stesso thread. La classe ClientRequestId mostra un esempio dell'uso della classe ThreadLocale: La classe ProductHandlerThreadLocal utilizza ClientRequestId per restituire lo stesso ID generato sullo stesso thread: Quando si esegue il metodo principale, l'output della console mostrerà ID diversi per ciascun thread. Ad esempio: se intendi utilizzare ThreadLocale, devi tenere conto di alcuni rischi di utilizzo quando unisci i thread (come nelle applicazioni server). Potrebbero verificarsi perdite di memoria o perdite di informazioni tra le richieste. Non entrerò troppo nei dettagli a riguardo perché... L’articolo “ Come darsi la zappa sui piedi con ThreadLocale ” dimostra bene come ciò possa accadere. 4. Utilizzo della sincronizzazione Un altro modo per fornire un accesso thread-safe agli oggetti è tramite la sincronizzazione. Se sincronizziamo tutti gli accessi a un riferimento, solo un oggetto thread alla volta accederà ad esso. Di questo parleremo nei post futuri. 5. Conclusione Abbiamo esaminato diversi metodi che consentono di creare oggetti semplici a cui possono accedere più thread. È molto più difficile prevenire errori multi-thread se un oggetto può avere più stati. D'altra parte, se un oggetto può avere un solo stato o nessuno, non dobbiamo preoccuparci che più thread accedano all'oggetto contemporaneamente. L'originale è qui . 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 ...
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION