Багатопотоковість
Багатопотоковість операційної системи – можливість одночасного виконання понад однієї програми.
Кількість одночасно виконуваних процесів не обмежена кількістю процесорів.
Багатопотокові програми розширюють ідею багатозадачності.
Індивідуальні застосунки можуть виконувати безліч завдань одночасно. Кожне завдання називається потоком – thread.
Істотна різниця між багатьма процесами та багатьма потоками полягає ось у чому:
• Кожен процес має власний набір змінних, потоки можуть розділити ті самі дані.
• Потоки є «легковажнішими», ніж процеси.
• Приклад багатопотокових застосунків: браузер, вебсервер, програми з графічним користувацьким інтерфейсом.
Створення потоків
Щоб створити новий потік виконання, необхідно створити об'єкт
Thread: Thread worker = New Thread();
Після того, як об'єкт-потік буде створено, можна задати його конфігурацію та запустити.
Коли потік готовий до роботи, варто викликати його метод start.
Метод start породжує новий потік, що виконується, на основі даних об'єкта класу Thread, після чого завершується.
Метод start також викликає метод run нового потоку, що приводить до активізації останнього.
Вихід із методу run означає припинення роботи потоку.
Інтерфейс Runnable
Потік є абстракцією поняття виконавця – суб'єкта, який спроможний виконати будь-які корисні дії.
План роботи, що підлягає виконанню, описується за допомогою інструкцій методу run.
Щоб певна мета була досягнута, необхідні виконавець і план роботи.
Інтерфейс Runnable абстрагує поняття роботи та дозволяє призначити її виконавцю – об'єкту потоку.
У складі інтерфейсу Runnable оголошено єдиний метод: interface
Runnable{
public void run();
}
Конструктори класу Thread
• public Thread(Runnable target) створює новий об'єкт Thread, який використовує метод run об'єкта target.
• public Thread(Runnable target, String name) створює новий об'єкт Thread із заданим ім'ям name, який використовує метод run об'єкта target.
• public Thread(ThreadGroup group, Runnable target) створює новий об'єкт Thread у вказаному об'єкті ThreadGroup, який використовує метод run об'єкта target.
• public Thread(ThreadGroup group, Runnable target, String name) створює новий об'єкт Thread із заданим ім'ям name у вказаному об'єкті ThreadGroup, який використовує метод run об'єкта target.
Методи sleep, yield
Є методи в класі Thread, які керують плануванням потоків у системі:
• Thread.setPriority(int i) встановлює пріоритетність потоків.
• public static void sleep(long millis) throws InterruptedException зупиняє роботу поточного потоку як мінімум на вказану кількість мілісекунд.
• public static void sleep(long millis, int nanos) throws InterruptedException – значення інтервалу в наносекундах лежить у діапазоні 0–999999.
• public static void yield() – поточний потік передає управління, щоб дати можливість працювати й іншим потокам, які виконуються.
Синхронізація
Коли два потоки мають скористатися одним і тим самим об'єктом, виникає небезпека, що накладання операцій призведе до руйнування даних.
Тому потоки мають синхронізувати свій доступ до критичних секцій.
Для цього в умовах багатопотоковості використовують блокування об'єкта.
Коли об'єкт заблоковано певним потоком, лише цей потік може працювати з ним.
Щоб клас міг використовуватись у багатопотоковому середовищі, необхідно оголосити відповідні методи з атрибутом synchronized.
Якщо певний потік викликає метод synchronized, відбувається блокування об'єкта. Виклик методу synchronized того ж об'єкта іншим потоком буде припинено до зняття блокування.
Методи wait, notify, notifyAll
• public final void wait(long timeout) throws InterruptedException – виконання поточного потоку припиняється до отримання повідомлення або до закінчення заданого інтервалу часу timeout.
Значення timeout визначається в мілісекундах. Якщо воно дорівнює нулю, то очікування не переривається за тайм-аутом, а продовжується до отримання повідомлення.
• public final void wait(long timeout, int nanos) throws InterruptedException – аналог попереднього методу з точнішим контролем часу; інтервал тайм-ауту є сумою двох параметрів: timeout (у мілісекундах) і nanos (у наносекундах, значення в діапазоні 0-999999).
• public final void wait() throws InterruptedException еквівалентний wait(0).
Problems with Threads
Взаємне блокування потоків (Deadlock)
Якщо є два потоки та два об'єкти, які підлягають блокуванню, виникає небезпека виникнення взаємоблокування – кожен із потоків володіє блокуванням одного об'єкта й очікує звільнення іншого об'єкта.
Якщо об'єкт X має synchronized-метод, який викликає synchronized-метод об'єкта Y, a Y, зі свого боку, також має синхронізований метод, що звертається до synchronized-методу об'єкта X, два потоки можуть перебувати в стані очікування взаємного завершення, щоб опанувати блокування, і жоден з них не виявиться спроможним продовжити роботу.
Така ситуація називається клінчем (Deadlock).
Синонімом Deadlock є Livelock, коли всі потоки займаються марною роботою і стан системи не змінюється з часом.
Завершення виконання потоку (Thread)
Про потік, який почав роботу, говорять як про дієвий (alive), і метод isAlive такого потоку повертає значення true.
Потік продовжує залишатися дієвим доти, доки не буде зупинений внаслідок виникнення однієї з трьох можливих подій:
• метод run завершив виконання нормальним чином;
• робота методу run перервана;
• викликаний метод destroy об'єкта потоку.
Завершення виконання потоку (Thread)
Про потік, який почав роботу, говорять як про дієвий (alive), і метод isAlive такого потоку повертає значення true.
Потік продовжує залишатися дієвим доти, доки не буде зупинений внаслідок виникнення однієї з трьох можливих подій:
• метод run завершив виконання нормальним чином;
• робота методу run перервана;
• викликаний метод destroy об'єкта потоку.
Методи переривання (Interrupt methods)
До механізму переривання роботи потоку мають стосунок такі методи:
• interrupt() посилає потоку повідомлення про переривання. Тобто встановлює прапор переривання. Прапор встановлюється лише за запущеного потоку.
• isInterrupted() перевіряє, чи було перервано роботу потоку викликом методу interrupt. Стан переривання не очищається, робота методу run перервана;I
• nterrupted() – статичний метод, що перевіряє, чи виконувалося переривання поточного потоку, 1 очищає «стан переривання» потоку.
Останнє може бути очищене лише самим потоком – «зовнішніх» способів скасування повідомлення про переривання, надіслане потоку, не існує.
Переривання через метод interrupt зазвичай не впливає на працездатність потоку, але деякі методи, як-от sleep і wait, бувши перерваними, викидають виняток типу interruptedException.
Потоки-демони (Thread-daemon)
Є два різновиди потоків – користувацькі (user) і потоки-демони (daemon).
Наявність користувацьких потоків зберігає застосунок у робочому стані.
Коли виконання останнього з користувацьких потоків завершується, діяльність всіх демонів переривається, а застосунок фінішує.
Переривання роботи демонів схоже на виклик методу destroy – воно відбувається раптово та не залишає потокам жодних шансів до виконання завершальних операцій, тому демони обмежені у виборі функціональних можливостей.
Для надання потоку статусу демона необхідно викликати метод setDaemon(true), який визначений у класі Thread.
Перевірити належність потоку до категорії демонів можна за допомогою методу isDaemon(). За замовчуванням статус демона успадковується потоком від потоку-батька в момент створення, а після старту не може бути змінений.
Спроба виклику setDaemon(true) під час роботи потоку призводить до викидання винятку типу IIlegalThreadStateException.
Метод main за замовчуванням породжує потоки зі статусом користувацьких
Кваліфікатор volatile (volatile qualifier)
Мова гарантує, що операції читання та запису будь-яких значень, окрім тих, що належать до типів long або double, завжди виконуються атомарно — відповідна змінна в будь-який момент часу міститиме тільки те значення, яке збережене певним потоком, але не якусь суміш результатів декількох різних операцій запису.
Однак атомарний доступ не гарантує, що потік завжди зможе рахувати останню версію значення, яке збережене у змінній.
Кваліфікатор volatile повідомляє компілятору, що значення змінної може бути змінено в непередбачуваний момент.
Розглянемо приклад:
int currentvalue = 5; for (;;) {
display.showValue(currentvalue);
Thread.sleep(1000); // заснути на одну секунду
}
Якщо метод showValue не має можливості змінити значення currentvalue, компілятор може висунути припущення про те, що всередині циклу for це значення можна трактувати як незмінне та використовувати одну й ту ж константу 5 на кожній ітерації циклу під час виклику showValue.
Але якщо вміст поля currentvalue під час виконання циклу піддається оновленню через інші потоки, припущення компілятора виявиться неправильним.
Тому правильно оголосити змінну currentvalue, використовуючи volatile
volatile int currentvalue = 5;
Q&A
Дякую всім за заняття!🙌🏻
❗️🎓Тема уроку: 8. Thread & Runnable. Problems with Threads
Нагадую, що дедлайн здачі домашніх робіт – до наступного уроку.
Якщо виникають складнощі, пишіть, допоможу із задоволенням 😌
Запис лекції тренер опублікує трохи пізніше 🖥
Не забудьте повторити матеріал та підготуватися до наступного уроку📚
Успіху і до зустрічі!🤩
OOP. 1
Назва
Кількостей
Ціна
Рік виготовлення
Виробник
Визначити найдорожчий товар на складі та надрукувати всі відомості про нього.
OOP. 2
Назва
Частота
Об'єм оперативної пам'яті
Наявність DVD ROM
Вартість
Обчислити середню вартість усіх комп'ютерів і надрукувати найменування комп'ютерів та їхню середню вартість.
OOP. 3
Прізвище
Рік народження
Посада
Зарплата
Освіта
Визначити кількість працівників - інженерів і надрукувати всі відомості про них.