JavaRush /Java блог /Random UA /Багатопотоковість: що роблять методи класу Thread

Багатопотоковість: що роблять методи класу Thread

Стаття з групи Random UA
Вітання! Сьогодні продовжуємо говорити про багатопотоковість. Розглянемо клас Thread та роботу його кількох методів. Раніше, коли ми вивчали методи класу, найчастіше просто писали це так: "назва методу" -> "що він робить".
Що роблять методи класу Thread - 1
З методами Thread так не вийде :) Їхня логіка складніша, і без кількох прикладів не розібратися.

Метод Thread.start()

Почнемо із повторення. Як ти, напевно, пам'ятаєш, створити потік можна успадкувавши свій клас від класу Threadі перевизначивши в ньому метод run(). Але сам він, звісно, ​​не запуститься. Для цього у нашого об'єкта викликаємо метод start(). Багатопотоковість: що роблять методи класу Thread - 2Давай згадаємо приклад із попередньої лекції:
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(). Багатопотоковість: що роблять методи класу Thread - 3Метод 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()

Багатопотоковість: що роблять методи класу Thread - 4Метод 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 варіанти:
  1. Якщо об'єкт перебував у момент очікування, наприклад, joinабо sleep, очікування буде перервано, і програма викине InterruptedException.
  2. Якщо ж потік у цей момент був у працездатному стані, об'єкт буде встановлений 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. Щоб закріпити знання, можеш переглянути цю відеолекцію про багатопоточність:
вона послужить чудовим додатковим матеріалом! Наприкінці, після огляду методів, у ній розповідається саме про те, що ми проходитимемо далі за курсом :) Успіхів!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ