1. Передавання параметрів у потоки
Коли ви запускаєте потік, часто хочеться передати йому якусь «роботу»: ім’я файлу, діапазон чисел або привітання. Потік без параметрів — це як кур’єр без адреси: бігає містом, але не знає, куди доставляти піцу.
Як це робиться?
У Java потоки зазвичай створюють двома способами:
- Успадкування від класу Thread
- Реалізація інтерфейсу Runnable (або використання лямбда-виразу)
Передавання параметрів найчастіше роблять через конструктор класу, що реалізує Runnable. Це просто, безпечно й зрозуміло. Зробіть поля final — і ви захистите параметри від змін з інших потоків. Використання сеттерів/публічних полів загрожує проблемами синхронізації.
Приклад 1: Потік із параметром через конструктор
Доопрацюємо навчальний застосунок: обробка замовлень користувачів в окремому потоці.
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("Обробляю замовлення: " + orderId + " у потоці " + Thread.currentThread().getName());
// Тут може бути тривала обробка...
}
}
Створюємо й запускаємо потоки:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
Очікуваний вивід:
Обробляю замовлення: ORDER-001 у потоці Thread-0
Обробляю замовлення: ORDER-002 у потоці Thread-1
Приклад 2: Потік із параметром через лямбда-вираз (Java 8+)
Якщо завдання просте, можна обійтися без окремого класу.
public class Main {
public static void main(String[] args) {
String user1 = "Вася";
String user2 = "Катя";
Thread thread1 = new Thread(() -> {
System.out.println("Привіт, " + user1 + " з " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("Привіт, " + user2 + " з " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
Чому не варто використовувати сеттери/геттери для передавання параметрів?
Якщо ви передаєте параметри через сеттери або публічні поля, виникає ризик, що інший потік змінить значення безпосередньо під час виконання. Це може призвести до важковловимих помилок. Краще зробити поля final і передавати їх через конструктор.
2. Пріоритети потоків
У Java кожен потік має пріоритет — ціле число від 1 до 10, яке підказує планувальнику (scheduler), наскільки «важливий» цей потік порівняно з іншими. За замовчуванням у всіх потоків пріоритет 5 (Thread.NORM_PRIORITY).
Важливо: пріоритет — це лише «натяк» для ОС. Це не гарантія, що потік із пріоритетом 10 працюватиме швидше за потік із пріоритетом 1. Усе залежить від ОС, її налаштувань і поточного навантаження.
Як встановити пріоритет потоку?
У класі Thread є методи:
- setPriority(int newPriority)
- getPriority()
І три стандартні константи:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5)
- Thread.MAX_PRIORITY (10)
Приклад 3: Встановлення пріоритету потоку
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Потік " + Thread.currentThread().getName() +
" з пріоритетом " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
Очікуваний вивід (порядок рядків не гарантується!):
Потік LowPriority з пріоритетом 1
Потік HighPriority з пріоритетом 10
Потік NormalPriority з пріоритетом 5
Чи впливає пріоритет на порядок виконання?
У більшості випадків — ні. Пріоритети можуть впливати на те, скільки часу процесор виділяє потоку, але не гарантують порядок старту або завершення. Не будуйте логіку програми на пріоритетах — використовуйте їх як «м’які» підказки, наприклад, щоб фоновий потік не заважав основному.
Таблиця: Константи пріоритетів потоків
| Константа | Значення | Опис |
|---|---|---|
|
1 | Найнижчий пріоритет |
|
5 | Звичайний пріоритет |
|
10 | Найвищий пріоритет |
3. Іменування потоків
Ім’я потоку — це «кличка» у світі багатозадачності. Коли у вас десятки потоків, простіше налагоджувати, якщо замість "Thread-7" у логу ви побачите "FileUploader-1". Це особливо важливо під час аналізу логів і пошуку помилок.
Як задати ім’я потоку?
Ім’я можна вказати прямо в конструкторі Thread або встановити методом setName. Дізнатися поточне ім’я — getName.
Thread t = new Thread(() -> {
System.out.println("Я працюю!");
}, "MyThreadName");
t.start();
Приклад 4: Іменовані потоки
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("Я потік: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("Я потік: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
Очікуваний вивід:
Я потік: Downloader
Я потік: Uploader
4. Практика: кілька потоків із різними параметрами, пріоритетами та іменами
Об’єднаємо все в одному прикладі: кожне замовлення — окремий потік із власним іменем і пріоритетом.
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] Почав обробку замовлення " + orderId +
" (пріоритет " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // імітація роботи
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] Перервано!");
}
System.out.println("[" + Thread.currentThread().getName() + "] Завершив обробку замовлення " + orderId);
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
Що побачимо:
- Кожен потік пише, яке замовлення він обробляє, своє ім’я та пріоритет.
- Час обробки імітується за допомогою Thread.sleep.
- Порядок завершення потоків може не збігатися з пріоритетом.
5. Важливі нюанси та особливості
Передавання параметрів: лише через конструктор!
Так безпечніше. Оголосіть параметри як final — їх не можна буде змінити після створення об’єкта, і жоден інший потік не «підсуне» свої значення. Це критично для запобігання гонкам даних.
Пріоритети: не будуйте на них бізнес-логіку
Пріоритети — це як прохання до офіціанта: «Можна мені каву швидше?». Іноді працює, іноді — ні. Це рекомендація для ОС, а не правило виконання.
Імена потоків: використовуйте для налагодження
Дає величезну економію часу під час аналізу логів. Звикайте одразу задавати змістовні імена.
6. Типові помилки під час роботи з параметрами та пріоритетами потоків
Помилка № 1: Передавання параметрів через публічні поля. Якщо ви оголошуєте параметри як звичайні поля й змінюєте їх після старту потоку, можна отримати непередбачувані результати. Використовуйте final-поля і конструктор.
Помилка № 2: Очікування, що пріоритет гарантує порядок. Виставили Thread.MAX_PRIORITY — і думаєте, потік буде завжди першим? Ні. Не використовуйте пріоритети для синхронізації або керування логікою.
Помилка № 3: Безіменні потоки в логах. Коли в логах лише «Thread-3», «Thread-7» — шукати винуватця складно. Надавайте потокам змістовні імена за допомогою конструктора або setName.
Помилка № 4: Використання одного й того самого об’єкта Runnable для кількох потоків. Якщо об’єкт Runnable зберігає стан і ви передаєте його кільком потокам, вони «ділять» параметри — прямий шлях до гонок даних. Створюйте окремий Runnable на потік.
Помилка № 5: Передавання параметрів через set-методи. Якщо ви задаєте параметри через set*-методи вже після створення/запуску потоку, значення можуть змінитися «на льоту» — класична race condition.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ