JavaRush /Курсы /JAVA 25 SELF /Параметры и приоритеты потоков

Параметры и приоритеты потоков

JAVA 25 SELF
51 уровень , 3 лекция
Открыта

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

Влияет ли приоритет на порядок выполнения?

В большинстве случаев — нет. Приоритеты могут повлиять на то, сколько времени процессор выделяет потоку, но не гарантируют порядок старта или завершения. Не стройте логику программы на приоритетах — используйте их как «мягкие» подсказки, например, чтобы фоновый поток не мешал основному.

Таблица: Константы приоритетов потоков

Константа Значение Описание
Thread.MIN_PRIORITY
1 Самый низкий приоритет
Thread.NORM_PRIORITY
5 Обычный приоритет
Thread.MAX_PRIORITY
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.

1
Задача
JAVA 25 SELF, 51 уровень, 3 лекция
Недоступна
Управление агентами в онлайн-маркетплейсе 🛍️
Управление агентами в онлайн-маркетплейсе 🛍️
1
Задача
JAVA 25 SELF, 51 уровень, 3 лекция
Недоступна
Координация роботов на умной фабрике 🏭
Координация роботов на умной фабрике 🏭
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ