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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ