JavaRush /Blog Java /Random-PL /Współbieżność w Javie. Samouczek - Konstrukcje bezpieczne...
0xFF
Poziom 9
Донецк

Współbieżność w Javie. Samouczek - Konstrukcje bezpieczne dla wątków.

Opublikowano w grupie Random-PL
Po omówieniu głównych zagrożeń związanych z uruchamianiem programów równoległych (takich jak atomowość lub widoczność ), przyjrzymy się niektórym projektom klas, które pomogą nam zapobiec wyżej wymienionym pułapkom. Niektóre z tych konstrukcji tworzą obiekty bezpieczne dla wątków, co pozwala nam bezpiecznie udostępniać je między wątkami. Jako przykład przyjrzymy się obiektom niezmiennym i bezstanowym. Inne widoki uniemożliwiają różnym wątkom modyfikowanie danych, takich jak zmienne lokalne wątku. Możesz zobaczyć wszystkie kody źródłowe na Githubie . 1. Obiekty niezmienne Obiekty niezmienne mają stan (posiadają dane reprezentujące stan obiektu), ale jest on ustawiany w konstruktorze w momencie tworzenia, gdy instancja obiektu została utworzona i stanu nie można zmienić. Chociaż wątki mogą się zmieniać, obiekt nadal ma jeden możliwy stan. Ponieważ wszystkie pola są tylko do odczytu, żaden wątek nie może zmienić danych obiektu. Z tego powodu niezmienny strumień jest z natury bezpieczny dla wątków. Klasa Product demonstruje klasę niezmienną. Wypełnia wszystkie swoje pola w konstruktorze i żadne z nich się nie zmienia: 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; } } w niektórych przypadkach ustawienie pól jako ostatecznych nie wystarczy. Na przykład klasa MutableProduct nie jest niezmienna, chociaż wszystkie pola są ostateczne: Dlaczego powyższa klasa nie jest niezmienna? Powodem jest to, że pozwalamy na pobranie referencji z klasy. Pole „ kategorie ” jest odniesieniem, które można modyfikować, więc po otrzymaniu klient może je zmodyfikować. Aby to zilustrować, rozważmy następujący program: I wynik konsoli: Ponieważ pole „kategorie” jest modyfikowalne i zostało uzyskane z obiektu, klient zmodyfikował tę listę. Obiekt, który powinien być niezmienny, został zmieniony, co spowodowało nowy stan. Jeśli chcesz przedstawić zawartość listy, możesz użyć niezmiennej reprezentacji listy: 2. Obiekty bezstanowe Obiekty bezstanowe są podobne do obiektów niezmiennych, ale w tym przypadku nie mają ani jednego stanu. Gdy obiekt jest obiektem bezstanowym, nie musi utrwalać żadnych danych pomiędzy wywołaniami. Ponieważ nie istnieje żaden stan, żaden wątek nie może wpłynąć na wynik innego wątku, wywołując metody obiektu. Z tego powodu obiekty bezstanowe są z natury bezpieczne dla wątków. Przykładem tego typu obiektu jest klasa ProductHandler . Zawiera wiele operacji na obiektach Product i nie przechowuje żadnych danych pomiędzy wywołaniami. Wynik operacji jest niezależny od poprzednich wywołań lub jakichkolwiek przechowywanych danych: w metodzie sumCart ProductHandler konwertuje listę produktów na tablicę do wykorzystania w pętli for-each w celu iteracji po wszystkich elementach. Lista iteratorów nie jest bezpieczna dla wątków i może zgłosić wyjątek ConcurrentModificationException , jeśli podczas iteracji nastąpią zmiany. W zależności od potrzeb możesz wybrać inną strategię . 3. Zmienne lokalne wątku Zmienne lokalne wątku to te zmienne, które są zdefiniowane w wątku. Żadne inne wątki ich nie widzą i nie będą ich zmieniać. Pierwszy typ to zmienne lokalne. W poniższym przykładzie zmienna total jest przechowywana na stosie wątku: 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; } Pamiętaj tylko, że jeśli zdefiniujesz referencję zamiast zmiennej pierwotnej i zwrócisz ją, opuści ona swoje granice. Możesz nie wiedzieć, gdzie został utworzony zwrócony link. Kod wywołujący metodę sumCart może przechowywać ją w polu statycznym i umożliwiać dostęp do niej różnym wątkom. Drugi typ to klasa ThreadLocal . Ta klasa zapewnia niezależną pamięć dla każdego wątku. Wartości przechowywane w ThreadLocal są dostępne dla dowolnego kodu w tym samym wątku. Klasa ClientRequestId przedstawia przykład użycia klasy ThreadLocale: Klasa ProductHandlerThreadLocal używa klasy ClientRequestId do zwrócenia tego samego wygenerowanego identyfikatora w tym samym wątku: Podczas wykonywania metody main dane wyjściowe konsoli pokażą różne identyfikatory dla każdego wątku. Na przykład: Jeśli zamierzasz używać ThreadLocale, musisz uważać na pewne ryzyko związane z użytkowaniem podczas łączenia wątków (jak w aplikacjach serwerowych). Pomiędzy żądaniami mogą wystąpić wycieki pamięci lub informacji. Nie będę się nad tym rozwodzić, bo... Artykuł „ Jak strzelić sobie w stopę za pomocą ThreadLocale ” dobrze pokazuje, jak to może się zdarzyć. 4. Korzystanie z synchronizacji Innym sposobem zapewnienia bezpiecznego dla wątków dostępu do obiektów jest synchronizacja. Jeśli zsynchronizujemy wszystkie dostępy do referencji, to w danym momencie tylko jeden obiekt wątku będzie miał do niej dostęp. Porozmawiamy o tym w przyszłych postach. 5. Wniosek Przyjrzeliśmy się kilku metodom umożliwiającym budowanie prostych obiektów, do których można uzyskać dostęp przez wiele wątków. Znacznie trudniej jest zapobiec błędom wielowątkowym, jeśli obiekt może mieć wiele stanów. Z drugiej strony, jeśli obiekt może mieć tylko jeden stan lub żaden, nie musimy się martwić, że wiele wątków będzie miało dostęp do obiektu w tym samym czasie. Oryginał jest tutaj . 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 ...
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION