Вітання! Сьогодні продовжуємо говорити про багатопотоковість. Розглянемо клас Thread та роботу його кількох методів. Раніше, коли ми вивчали методи класу, найчастіше просто писали це так: "назва методу" -> "що він робить".
З методами Thread так не вийде :) Їхня логіка складніша, і без кількох прикладів не розібратися.
вона послужить чудовим додатковим матеріалом! Наприкінці, після огляду методів, у ній розповідається саме про те, що ми проходитимемо далі за курсом :) Успіхів!
Метод Thread.start()
Почнемо із повторення. Як ти, напевно, пам'ятаєш, створити потік можна успадкувавши свій клас від класуThread
і перевизначивши в ньому метод run()
. Але сам він, звісно, не запуститься. Для цього у нашого об'єкта викликаємо метод start()
. Давай згадаємо приклад із попередньої лекції:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Выполнен поток " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Зверніть увагу: щоб запустити потік, необхідно викликати спеціальний методstart()
, а не методrun()
! Цю помилку легко припустити, особливо на початку вивчення багатопоточності. Якщо в нашому прикладі ти 10 разів викличеш у об'єкта методrun()
замістьstart()
результат буде таким:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
Виконаний потік Thread-0 Виконаний потік Thread-1 Виконаний потік Thread-2 Виконаний потік Thread-3 Виконаний потік Thread-4 Виконаний потік Thread-5 Виконаний потік Thread-6 Виконаний потік Thread-7 Виконаний потік Thread-8 Виконаний потік Thread-9 Подивися на послідовність виведення: все йде строго по порядку. Дивно, правда? Ми до такого не звикли, адже вже знаємо, що порядок запуску та виконання потоків визначає надрозум усередині нашої операційної системи — планувальник потоків. Може, просто пощастило? Звичайно, річ не в везінні. У цьому можеш переконатися, запустивши програму ще кілька разів. Справа в тому, що прямий виклик методуrun()
не має відношення до багатопоточності. У цьому випадку програма буде виконана в головному потоці - тому, в якому виконується методmain()
. Він просто послідовно виведе 10 рядків на консоль та все. Жодні 10 потоків не запустяться. Тому запам'ятай на майбутнє та постійно себе перевіряй. Хочеш, щоб виконався run()
, викликай start()
. Поїхали далі.
Метод Thread.sleep()
Для припинення виконання поточного потоку на якийсь час використовуємо методsleep()
. Метод sleep()
приймає як параметр число мілісекунд, тобто той час, який необхідно «приспати» потік.
public class Main {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
System.out.println(" - Сколько я проспал? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " секунды");
}
}
Висновок у консоль: – Скільки я проспав? - 3 секунди Зверніть увагу: метод sleep()
– статичний: він присипляє поточний потік. Тобто той, який працює зараз. Ще один важливий аспект: потік у стані сну можна перервати. У такому разі у програмі виникне виняток InterruptedException
. Ми розглянемо приклад нижче. До речі, а що станеться після того, як потік прокинеться? Чи продовжить він одразу своє виконання з того місця, де закінчив? Ні. Після того, як потік «прокидається» — коли закінчується час, переданий як аргумент в Thread.sleep()
, — він переходить у стан runnable, "працездатний". Однак це не означає, що планувальник потоків запустить саме його. Цілком можливо, він віддасть перевагу якомусь іншому «несплячому» потоку, а наш «свіжопрокинувся» продовжить роботу трохи пізніше. Обов'язково запам'ятай: «прокинувся — значить продовжив працювати в ту саму секунду»!
Метод Thread.join()
Методjoin()
припиняє виконання поточного потоку доти, доки завершиться інший потік. Якщо у нас є 2 потоки, t1
і t2
, і ми напишемо
t1.join()
t2
не розпочне роботу, доки t1 не завершить свою. join()
Можна використовувати метод , щоб гарантувати послідовність виконання потоків. Давай розглянемо роботу join()
на прикладі:
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Начало работы потока " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Поток " + getName() + " завершил работу.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/*Второй поток t2 начнет выполнение только после того, як будет завершен
(або бросит исключение) первый поток - t1*/
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
//Главный поток продолжит работу только после того, як t1 и t2 завершат работу
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Все потоки закончабо работу, программа завершена");
}
}
Ми створабо простий клас ThreadExample
. Його завдання - вивести на екран повідомлення про початок роботи, потім заснути на 5 секунд і наприкінці повідомити про завершення роботи. Нічого складного. Головна логіка укладена у класі Main
. Подивись на коментарі: за допомогою методу join()
ми успішно керуємо послідовністю виконання потоків. Якщо ти згадаєш початок теми, то цим займався планувальник потоків. Він запускав їх на власний розсуд: щоразу по-різному. Тут ми за допомогою методу гарантували, що спочатку буде запущено і виконано потік t1
, потім -t2
, і лише після них – головний потік виконання програми. Йдемо далі. У реальних програмах тобі неодноразово зустрінуться ситуації, коли необхідно буде перервати виконання якогось потоку. Наприклад, наш потік виконується, але при цьому чекає певної події чи виконання умови. Якщо це сталося, він зупиняється. Було б, мабуть, логічно, якби існував якийсь метод типу stop()
. Але все не так просто. Колись давно метод Thread.stop()
Java дійсно був і дозволяв переривати роботу потоку. Але пізніше його видалабо з бібліотеки Java. Ти можеш знайти його в документації Oracle і побачити, що він помічений як deprecated. Чому? Тому що він просто зупиняв потік без будь-якої додаткової роботи. Наприклад, потік міг працювати з даними і щось у них міняти. Потім його різко вирубували шляхом stop()
посеред роботи — і все. Ні коректного завершення роботи, ні звільнення ресурсів, ні хоча б обробки помилок нічого цього не було. Метод stop()
, якщо перебільшувати, просто трощив все на своєму шляху. Його роботу можна порівняти з тим, як хтось висмикує вилку з розетки, щоб вимкнути комп'ютер. Так, потрібного результату досягти можна. Але всі розуміють, що за кілька тижнів комп'ютер не скаже за це «дякую». Тому логіку переривання потоків в Java змінабо, і тепер використовується спеціальний метод — interrupt()
.
Метод Thread.interrupt()
Що станеться, якщо у потоку викликати методinterrupt()
? Є 2 варіанти:
- Якщо об'єкт перебував у момент очікування, наприклад,
join
абоsleep
, очікування буде перервано, і програма викинеInterruptedException
. - Якщо ж потік у цей момент був у працездатному стані, об'єкт буде встановлений boolean-прапор
interrupted
.
Thread
є спеціальний метод boolean isInterrupted()
. Давай повернемося наприклад з годинником, який був у лекції основного курсу. Для зручності він трохи спрощений:
public class Clock extends Thread {
public static void main(String[] args) throws InterruptedException {
Clock clock = new Clock();
clock.start();
Thread.sleep(10000);
clock.interrupt();
}
public void run() {
Thread current = Thread.currentThread();
while (!current.isInterrupted())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Работа потока была прервана");
break;
}
System.out.println("Tik");
}
}
}
У нашому випадку годинник стартує і починає цокати кожну секунду. На 10-й секунді ми перериваємо потік годинника. Як ти вже знаєш, якщо потік, який ми намагаємося перервати, знаходиться в одному зі станів очікування, це призводить до InterruptedException
. Даний вид виключення є перевіреним, тому його можна легко перехопити і виконати нашу логіку завершення програми. Що ми зробабо. Ось наш результат: Tik Tik Tik Tik Tik Tik Tik Tik Робота потоку була перервана На цьому ми закінчуємо знайомство з основними методами класу Thread
. Щоб закріпити знання, можеш переглянути цю відеолекцію про багатопоточність:
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ