Стан потоків у багатопоточності
Потік може бути в одному з наступних станів:
- New- об'єкт класу Thread створено, але ще не запущено. Він ще є потоком виконання і природно не виконується.
- Runnable- Потік готовий до виконання, але планувальник ще не вибрав його.
- Running- Потік виконується.
- Waiting/blocked/sleeping - потік блокований або потік чекає на закінчення роботи іншого потоку.
- Dead- Потік завершений. Буде викинуто виняток при спробі викликати метод start() для dead потоку.
Існують перерахування Thread.State, Що містить значення можливих станів потоку: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.
Отримати поточне значення стану потоку можна викликом методу getState() класу Thread.
Граф стану потоків
- Багатозадачність vs. багатопоточність
- Інтерфейс Runnable та клас Thread у Java
- Перемикання між потоками
- Пріоритети потоків у Java
- Метод Thread.sleep()
- Метод Thread.yield()
- Метод Thread.join() в Java
- Метод Thread.isAlive()
- Потокобезпечні класи
- Синхронізація потоків
- Взаємне блокування або дідлок
- Міжпоточні комунікації
- Завдання
Java. Потоки виконання. Методи класу Thread
Потоки виконання. Методи класу Thread : join() , isAlive() , getPriority() , setPriority()
Перед вивченням цієї теми рекомендується ознайомитись з такими темами:
Пошук на інших ресурсах:
1. Чому потрібно, щоб основний (викликаючий) потік завершувався першим? Пояснення. Метод join(). Необхідність застосування, загальна форма
Нехай задані два потоки:
- основний (що викликає). З основного потоку запускається (викликається) дочірній потік;
- дочірній (викликаний).
Для того, щоб з дочірнього потоку отримати результат (об'єкт, дані), основний потік, що викликає, повинен завершуватися останнім.Якщо основний потік завершується швидше за дочірній поток, то, швидше за все, будуть виходити нульові (початкові) або проміжні значення з дочірнього потоку, що є помилкою.
Щоб основний потік завершувався останнім у мові Java використовується метод join() . Загальна форма методу:
public final void join() throws InterruptedException
- InterruptedException – клас виключення, що виникає, коли якийсь інший потік перервав поточний потік. Після цього виключення перерваний статус поточного потоку скидається.
2. Метод join(). приклад
Завдання. Здійснити виклик дочірнього потоку з основного потоку. У дочірньому потоці визначається максимальне значення масиву цілих чисел. Отриманий результат (максимальне значення) повинен повертатися до основного потоку і виводитися на екран.
Рішення. Для створення дочірнього потоку розробляється окремий клас. У нашому випадку клас має ім'я Thread1. У цьому класі оголошуються всі необхідні елементи:
- посилання thr на дочірній потік типу Thread;
- масив AI цілих чисел;
- змінна maximum, яка є результатом роботи потоку;
- конструктор. У конструкторі здійснюється початкова ініціалізація масиву і створення і запуск дочірнього потоку. Запуск потоку виконується методом start(). Якщо в конструкторі не реалізувати створення та запуск дочірнього потоку, то методи екземпляра класу виконуватимуться в основному потоці;
- метод run(), який визначений в інтерфейсі Runnable. Оскільки клас Thread1 реалізує цей інтерфейс, то ньому потрібно обов'язково реалізовувати метод run() . У методі run() вписується основний код розв'язання задачі — код пошуку максимального значення;
- методи доступу до поля класу, значення яких використовується в основному потоці.
У цьому основним потоком служить функція main() , у якій необхідно виконати такі шаги:
- оголосити масив, що тестується;
- створити дочірній потік (примірник класу Thread1) та отримати посилання на нього;
- запустити метод join() для того, щоб основний потік завершився останнім;
- після завершення дочірнього потоку прочитати результат, отриманий у цьому потоці.
Текст роботи програми є наступним.
// Клас, що реалізує дочірній потік у цій задачі
class
Thread1 implements Runnable < private Thread thr; // Посилання на дочірній потік
private
int[] AI;
private
int
maximum; //Результат виконання потоку
// Конструктор - отримує масив цілих чисел
public Thread1(int[] _AI) < // Ініціалізація масиву
AI = _AI; // Створити потік
thr = new Thread(this, "Thread1."); // Запустити потік виконання
thr.start(); > // Метод, який вписаний код виконання потоку.
// У нашому випадку вписується код пошуку мінімального значення
// та заповнення змінної maximum.
public
void run() < int max = AI[0]; for (int
i=1; iAI.length; i++) if (maxAI[i]) max = AI[i]; maximum = max; > // Методи доступу до полів класу
public Thread getThread() < return
thr; >
public
int getMax() < return
maximum; > >
public
class TrainThreads2 <
public
static
void main(String[] args) < // 1. Оголосити тестований масив
int[] AI = <2, 3, 4, 8, -1>; // 2. Створити дочірній потік, отримати посилання нього Thread1 t1 = new Thread1(AI); // 3. Прочитати результат
try
< // Очікування завершення потоку t1 - обов'язково,
// інакше можна отримати нулі t1.getThread().join(); > catch (InterruptedException e) < System.out.println("Error."); >
// Прочитати результат після завершення потоку t1 System.out.println("max color: #0000ff;">main() прибрати код
try
< // Очікування завершення потоку t1 - обов'язково,
// інакше буде отримано нулі t1.getThread().join(); > catch
(InterruptedException e) < System.out.println("Error."); >
то буде отримано наступний результат
Це означає, що основний потік завершується першим, не дочекавшись завершення дочірнього потоку. У цьому випадку з дочірнього потоку повертається первісне значення змінної maximum в екземплярі t1. А це помилка.
Результат виконання програми
3. Метод isAlive() . Визначити, чи виконується потік. приклад
Метод isAlive() призначений визначення існування (виконання) потоку. Загальна форма методу:
final boolean isAlive()
Метод повертає true якщо потік, для якого викликаний метод, ще виконується. В іншому випадку він повертає false.
приклад. У прикладі за допомогою методу isAlive() очікується завершення дочірнього потоку з метою отримання результату.
У дочірньому потоці обчислюється сума елементів масиву чисел типу double. Щоб отримати коректну суму, потрібно, щоб головний потік завершився останнім.
// Клас, що інкапсулює потік виконання
class
SumArray implements Runnable < Thread thr; // Посилання на поточний потік
double[]
AD; // Посилання на внутрішній масив
double
summ; //Результат роботи потоку
// Конструктор класу SumArray - отримує назву потоку та масив SumArray(double[] AD, String name) < // Присвоїти масиву AD зовнішнє посилання
this.AD = AD; // Створити екземпляр thr та встановити йому назву потоку
thr = new Thread(this, name); // Запустити потік виконання
thr.start(); // Викликається метод run() >
/ / Точка входу в потік - метод run ()
public
void run() < // Повідомлення про початок виконання потоку – обчислення суми масиву System.out.println("SumArray.run() - begin"); // у потоці обчислити суму елементів масиву AD
// і записати її в змінну summ
summ = 0; for (int
i=0; i try
< // зробити паузу, щоб могли отримувати управління інші потоки Thread.sleep(50); summ = summ + AD[i]; >
catch
(InterruptedException e) < System.out.println(e.getMessage()); > > // Повідомлення про завершення потоку System.out.println("SumArray.run() - end"); >
// Метод, що повертає поточний потік Thread getThread() < return
thr; >
// Метод, який повертає суму елементів
double getSumm() < return
summ; > >
public
class Threads <
public
static
void main(String[] args) < // Демонстрація методу isAlive()
// 1. Оголосити масив, котрій потрібно обчислити суму
double[] AD = <1.1, 2.2, 3.3, 4.4, 5.5, 6.6>; // 2. Створити екземпляр класу SumArray SumArray sa = new SumArray(AD, "SumArray thread"); // запускається потік
// 3. Викликати метод isAlive() в циклі для того,
// щоб дочекатися закінчення потоку sa
while (sa.getThread().isAlive()); // 4. Після закінчення дочірнього потоку отримати суму
double summ = sa.getSumm(); System.out.println("summ color: #800080;">while (sa.getThread().isAlive());
Якщо закоментувати цей рядок, програма може видати нульове значення суми. Це з тим, що головний потік завершиться до завершення дочірнього потоку.
Результат виконання програми
SumArray.run() - begin SumArray.run() - end summ = 23.1
4. Методи setPriority(), getPriority(). Встановити та отримати пріоритет потоку. приклад
Як відомо, для потоків виконання може бути встановлений різний пріоритет. Потоки з великим пріоритетом отримують більшу частину часу центрального процесора на виконання.
У мові Java існує можливість встановлення пріоритету потоків. Для цього використовуються два методи:
- setPriority() - встановлює значення пріоритету потоку;
- getPriority() - читає (отримує) значення пріоритету потоку.
У документації Java загальна форма методів:
final
void setPriority(int level) final
int getPriority()
- level – рівень пріоритету, що задається в межах констант від MIN_PRIORITY до MAX_PRIORITY. Значення MIN_PRIORITY = 1 значення MAX_PRIORITY = 10. Якщо потрібно встановити за замовчуванням значення пріоритету, для цього використовується статична константа NORM_PRIORITY , яка дорівнює 5.
приклад. У прикладі демонструється створення трьох потоків із різними пріоритетами. У кожному потоці створюється масив цілих випадкових чисел. Для інкапсуляції потоку реалізований клас RandomArray, який містить наступні складові:
- внутрішнє поле thr - Посилання на поточний потік виконання;
- внутрішнє поле AI - Посилання на масив чисел;
- конструктор RandomArray() . У конструкторі задається пріоритет потоку та здійснюється запуск потоку на виконання;
- метод run(), у якому генерується масив випадкових чисел. Цей метод є точкою входу до потоку;
- методи доступу до внутрішніх полів getThread() та getArray() ;
- метод getPriority(), який повертає пріоритет потоку виконання. Цей метод звертається до однойменного методу екземпляра thr для отримання числового значення пріоритету.
// Клас, що інкапсулює потік виконання
class RandomArray implements Runnable < private Thread thr; // Посилання на поточний потік
private
int[] AI; // масив чисел
// Конструктор класу SumArray.
// Конструктор отримує параметри:
// – name – назва потоку;
// – size – розмір масиву, який потрібно згенерувати в потоці;
// - Priority - пріоритет потоку. RandomArray(String name, int size, int priority) < // 1. Створити екземпляр потоку thr та задати йому назву потоку
thr = new Thread(this, name); // 2. Виділити пам'ять масиву AI
AI = new
int[Size]; // 3. Встановити пріоритет
// Перевірка значення priority на коректність
if ((Priority>=Thread.MIN_PRIORITY)&&(priorityMAX_PRIORITY)) thr.setPriority(priority);
else
// якщо некоректно priority, то встановити пріоритет за замовчуванням
thr.setPriority(Thread.NORM_PRIORITY); // Запустити потік виконання
thr.start(); // Викликається метод run() - генерується масив чисел >
/ / Точка входу в потік - метод run ()
public
void run() < // Повідомлення про початок виконання потоку – вказується ім'я потоку System.out.println(thr.getName() + "- begin"); // у потоці заповнити масив цілих чисел значеннями від 1 до AI.length
for (int
i=0; iAI.length; i++) < try
< // зробити паузу, щоб отримати управління інші потоки Thread.sleep(10); AI[i] = (int) (Math.random()*AI.length+1); > catch (InterruptedException e) < System.out.println(e.getMessage()); > > // Повідомлення про завершення потоку System.out.println(thr.getName() + "- end"); >
// Метод, який повертає посилання на поточний потік Thread getThread() < return
thr; >
// Метод, який повертає посилання масив чисел
int[] getArray() < return
AI; >
// Метод, який повертає пріоритет потоку
int getPriority() < // Викликається getPriority()
return
thr.getPriority(); > // Метод, що виводить масив на екран
void Print(String text) < System.out.print(text + "."); System.out.print("AI color: #800080;">for (int
i=0; iAI.length; i++) < System.out.print(AI[i] + " "); > System.out.println(); > > public
class Threads <
public
static
void main(String[] args) < try
< // 1. Створити три потоки з різними пріоритетами RandomArray A1 = new RandomArray("A1", 1000, 7); RandomArray A2 = new RandomArray("A2", 1000; 2); RandomArray A3 = new RandomArray("A3", 1000, 22); // Дочекатися завершення потоків A1.getThread().join(); A2.getThread().join(); A3.getThread().join(); // 2. Вивести пріоритети потоків контролю System.out.println("A1 priority color: #0000ff;">out.println("A2 priority color: #0000ff;">out.println("A3 priority color: #800080;">catch (InterruptedException e) < System.out.println(e.getMessage()); > > >
Результат виконання програми
A2 - begin A3 - begin A1 - begin A1 - end A3 - end A2 - end A1 priority = 7 A2 priority = 2 A3 priority = 5
Пов'язані теми
Вчимося програмувати на Java з нуля
Через потокову модель Java представлені найзагальніші властивості, пов'язані з багатопоточним програмуванням. Вся краса роботи з потоками реалізується через клас Thread. Клас має кілька статичних методів для роботи з потоками. Ці методи перелічені у таблиці 1.1.
| Метод |
Опис |
| getId() |
Методом як результат повертається ідентифікатор потоку - унікальне ціле число, що генерується при створенні потоку. |
| getName() |
Метод як результат повертає ім'я потоку, з якого викликається цей метод. |
| getPriority() |
Методом як результат повертає пріоритет потоку. |
| getThreadGroup() |
Методом значення повертається група, до якої належить поточний потік. |
| holdsLock() |
Метод як значення повертає логічне значення. Значення дорівнює true, якщо метод утримує монітор об'єкта, переданого як аргумент методу. Інакше повертається значення false. Монітор – об'єкт, який використовується для блокування ресурсу потоком та недопущення одночасного звернення до ресурсу різних потоків. Концепція монітора використовується для синхронізації потоків. |
| interrupt() |
Методом виконується переривання потоку |
| isAlive() |
Метод використовується для того, щоб визначити, чи потік використовується. |
| join() |
Метод-інструкція очікування завершення потоку. |
| run() |
Метод визначає точку входу до потоку. Програмний код потоку визначається шляхом перевизначення цього методу. |
| setName() |
Методом визначається ім'я потоку (ім'я потоку вказується у вигляді текстового рядка аргументом методу). |
| setPriority() |
Методом визначається пріоритет потоку (пріоритет передається аргументом методу). |
| sleep() |
Метод використовується для зупинення виконання потоку.Час (у мілісекундах), на яке виконується призупинення роботи потоку, вказується аргументом методу. |
| start() |
Метод запуску потоку При викликі цього методу автоматично запускається на виконання метод. run(), оголошений в інтерфейсі Runnable. |
| wait() |
Метод переводить потік у режим очікування. |
| yield() |
Метод тимчасово припиняє поточний потік та дозволяє виконання інших потоків. |
Тут слід дати деякі пояснення щодо виконання та не виконання потоків. Оскільки потоки в загальному випадку виконуються паралельно (одночасно), то, як кажуть, можливі варіанти. до системних ресурсів, до уваги приймається такий важливий показник, як пріоритет потоку.
Пріоритет потоку — це число, яке визначає «важливість» цього потоку. Саме по собі значення пріоритету не є важливим. вищим пріоритетом може перервати виконання менш пріоритетного потоку.
Роботу потоку, окрім остаточного та безповоротного завершення, можна призупинити.
Адже в процесі виконання потоки можуть (і, як правило, так і відбувається) звертатися до одних і тих самих ресурсів. .