JavaRush /Java блог /Random UA /Паралелізм у Java. Підручник – потокобезпечні конструкції...
0xFF
9 рівень
Донецк

Паралелізм у Java. Підручник – потокобезпечні конструкції.

Стаття з групи Random UA
Після розгляду основних ризиків під час роботи паралельних програм (таких як атомарність чи видимість ), ми подивимося деякі конструкції класів, які допоможуть нам запобігти вищезгадані помилки. Деякі з цих конструкцій створюють безпечні об'єкти, що дозволяє нам безпечно ділитися ними між потоками. Як приклад ми розглянемо незмінні та stateless об'єкти. Інші уявлення запобігатимуть модифікації даних різними потоками, таких як локальні змінні потоки. Ви можете переглянути всі вихідні коди на гітхабі . 1. Незмінні об'єкти Незмінні об'єкти мають стан (мають дані, які представляють стан об'єкта), але воно задано під час створення в конструкторі, як тільки екземпляр об'єкта був створений і стан не може бути змінено. Хоча потоки можуть чергуватись, об'єкт все одно має один можливий стан. Оскільки всі поля доступні лише для читання, жоден потік не зможе змінити дані об'єкта. Через це незмінний потік за своєю суттю є безпечним. Клас Product демонструє незмінний клас. Він заповнює всі свої поля в конструкторі і жодне з них не змінюється: 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; } } У деяких випадках недостатньо зробити поля final. Наприклад, клас MutableProduct не є незмінним, хоча всі поля final: Чому клас вище не незмінний? Причина в тому, що дозволяємо отримати посилання з класу. Поле ' categories ' – це змінне посилання, так що після отримання її клієнт може змінювати її. Для того, щоб показати це, розглянемо наступну програму: І висновок у консоль: Оскільки поле 'categories' змінюване і отримане з об'єкта, клієнт змінив цей список. Об'єкт, який має бути незмінним, був змінений, це призводить до нового стану. Якщо ви хочете представити вміст списку, ви можете використовувати незмінне подання списку: 2. Stateless об'єкти Stateless об'єкти схожі на незмінні об'єкти, але в цьому випадку вони не мають стану, навіть одного. Коли об'єкт є stateless-об'єктом, він не повинен зберігати будь-які дані між викликами. Оскільки немає жодного стану, то жоден потік неспроможна вплинути результат іншого потоку викликаючи методи об'єкта. З цієї причини Stateless об'єкти за своєю суттю потокобезпечні. Клас ProductHandler є прикладом цього об'єктів. Він містить кілька операцій над об'єктами Product, і він зберігає жодних даних між викликами. Результат операції не залежить від попередніх викликів або будь-яких даних, що зберігаються: У методі sumCart ProductHandler перетворює список Product в масив для використання в циклі for-each для перебору всіх елементів. Список ітераторів не є потокобезпечним і може спричинити виключення ConcurrentModificationException , якщо будуть зміни під час ітерації. Залежно від ваших потреб ви можете вибрати іншу стратегію . 3. Локальні змінні потоку Локальні змінні потоку – це змінні, які визначені у межах потоку. Жодні інші потоки не бачать їх і не змінять їх. Перший тип – це локальні змінні. У наведеному прикладі змінна total зберігається в стеку потоку: 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; } Просто майте на увазі, що якщо замість примітивної змінної ви визначите посилання і повернете його, воно залишить свої межі. Ви можете не знати, де повернуте посилання було створено. Код, який викликає метод sumCart , міг зберігати його в статичному полі і дозволити йому бути доступним різним потокам. Другий тип - це клас ThreadLocal . Цей клас забезпечує незалежне зберігання кожного потоку. Значення, збережені в ThreadLocal, доступні для будь-якого коду в тому ж потоці. Клас ClientRequestId показує приклад використання ThreadLocale класу: Клас ProductHandlerThreadLocal використовує ClientRequestId, щоб повернути той же згенерований ідентифікатор у тому ж потоці: При виконанні методу main виведення на консоль показуватиме різні ідентифікатори для кожного потоку. Наприклад: Якщо ви збираєтеся використовувати ThreadLocale, ви повинні піклуватися про деякі ризики використання при об'єднанні потоків (як у серверних додатках). Ви можете отримати витоку пам'яті або витік інформації між запитами. Я не сильно поширюватимуся з цього приводу т.к. стаття “ Як вистрілити собі в ногу з ThreadLocale ” добре демонструє як це може статися. 4. Використання синхронізації Інший спосіб забезпечення безпечного доступу до об'єктів – через синхронізацію. Якщо ми синхронізуємо всі доступи до посилання, лише один об'єкт потік буде звертатися до нього в даний момент часу. Ми обговоримо це у майбутніх постах. 5. Висновок Ми розглянули кілька методів, що дозволяють будувати прості об'єкти, які можуть бути доступні кільком потокам. Набагато важче запобігти багатопотокові помилки, якщо об'єкт може мати кілька станів. З іншого боку, якщо об'єкт може мати лише один стан або не мати жодного, ми можемо не турбуватися про доступ декількох потоків до об'єкта в один і той же час. Оригінал тут . 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 ...
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ