← Назад к вопросам
Зачем нужен метод run() в классе Thread?
1.0 Junior🔥 251 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен метод run() в классе Thread?
Метод run() — это контрольная точка, которая определяет, какой код должен выполняться в отдельном потоке. Это основной механизм для определения поведения потока в Java.
Базовое понимание
Thread vs run():
- Класс Thread — это конструкция Java для создания новых потоков
- Метод run() — это определение того, ЧТО будет делать этот поток
// Создание класса, расширяющего Thread
public class MyThread extends Thread {
@Override
public void run() {
// Этот код выполняется в отдельном потоке
System.out.println("Выполняется в потоке: " + Thread.currentThread().getName());
}
}
// Использование
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Запускает run() в новом потоке
// Главный поток продолжает выполняться параллельно
}
Критичное различие: start() vs run()
❌ НЕПРАВИЛЬНО: вызвать run() напрямую
MyThread thread = new MyThread();
thread.run(); // ОШИБКА!
// Это просто вызывает метод в ТЕКУЩЕМ потоке
// Никакого нового потока не создается
// Выполняется синхронно, как обычный метод
✅ ПРАВИЛЬНО: вызвать start()
MyThread thread = new MyThread();
thread.start(); // Создает новый поток и вызывает run() в нем
// Главный поток продолжает выполняться параллельно
Как это работает изнутри
Когда вы вызываете start():
- JVM создает новый поток операционной системы
- JVM регистрирует run() как точку входа для этого потока
- Когда ОС начинает выполнять этот поток, JVM вызывает run()
- Главный поток и новый поток работают параллельно
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start(); // Создает новый поток
System.out.println("Главный поток продолжает"); // Выполняется сразу
thread.join(); // Ждет, пока thread закончит
System.out.println("Все потоки завершены");
}
// Output:
// Главный поток продолжает
// Выполняется в потоке: Thread-0
// Все потоки завершены
Почему нельзя вызвать run() напрямую?
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
// ❌ Вызов run() напрямую
long start = System.currentTimeMillis();
thread.run(); // Выполняется в главном потоке
System.out.println("run() заняла: " +
(System.currentTimeMillis() - start) + " ms");
System.out.println("Главный поток всё еще заблокирован!");
// ✅ Правильное использование start()
start = System.currentTimeMillis();
thread.start(); // Создает новый поток
System.out.println("start() заняла: " +
(System.currentTimeMillis() - start) + " ms");
System.out.println("Главный поток продолжает параллельно!");
thread.join();
}
}
Реализация Runnable интерфейса
Зачасто используется Runnable вместо наследования Thread:
// Способ 1: Наследование Thread (используется редко)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Поток выполняется");
}
}
MyThread thread = new MyThread();
thread.start();
// Способ 2: Реализация Runnable (предпочтительно)
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Поток выполняется");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
// Способ 3: Lambda (Java 8+)
Thread thread = new Thread(() -> {
System.out.println("Поток выполняется");
});
thread.start();
Жизненный цикл потока и run()
NEW --> RUNNABLE --> RUNNING --> TERMINATED
↑ ↓
+-- WAITING -----+
+-- BLOCKED -----+
Вот как run() вписывается в этот цикл:
MyThread thread = new MyThread();
// Состояние: NEW
thread.start();
// Состояние: RUNNABLE (планировщик выберет это время)
// Когда планировщик переведет в RUNNING:
// --> JVM автоматически вызывает run()
// Выполняется код в run()
// Когда run() завершается --> TERMINATED
Обработка исключений в run()
public class MyThread extends Thread {
@Override
public void run() {
try {
// код
int result = 10 / 0; // ArithmeticException
} catch (Exception e) {
// Очень важно ловить исключения!
// Если не поймаешь, поток умрет молча
System.err.println("Ошибка в потоке: " + e);
}
}
}
// Альтернатива: UncaughtExceptionHandler
thread.setUncaughtExceptionHandler((thread1, exception) -> {
System.err.println("Необработанное исключение: " + exception);
});
Практический пример: Загрузка данных в фоне
public class DataLoader extends Thread {
private List<String> data;
@Override
public void run() {
System.out.println("Начинаем загрузку в потоке: " +
Thread.currentThread().getName());
try {
data = downloadData(); // Дорогая операция
System.out.println("Загрузка завершена");
} catch (Exception e) {
System.err.println("Ошибка загрузки: " + e);
}
}
public List<String> downloadData() throws Exception {
Thread.sleep(3000); // Имитация загрузки
return Arrays.asList("data1", "data2");
}
}
// Использование
public static void main(String[] args) throws InterruptedException {
DataLoader loader = new DataLoader();
loader.start(); // Загрузка начинается в фоне
System.out.println("Главный поток работает дальше");
loader.join(); // Ждем завершения
System.out.println("Главный поток: загрузка закончена");
}
Итоги
Метод run() нужен для:
- Определения кода — что должен выполнять поток
- Отделения логики — отделить поведение потока от его управления
- Контракта — всё, что расширяет Thread или реализует Runnable, ДОЛЖНО определить run()
- Точки входа — JVM знает, какой метод вызывать при запуске потока
Критично помнить: start() создает поток и вызывает run(), а run() вызванный напрямую — это просто синхронный вызов метода!