Thread и Runnable: создание потока
В исходной форме заметки было два способа: унаследовать Thread или передать Runnable. Оба работают, но решают разные задачи:
Thread— механизм исполнения и его жизненный цикл;Runnable— задача без результата.
Отдельный Thread
Thread worker = new Thread(
() -> System.out.println(Thread.currentThread().getName()),
"report-worker"
);
worker.start();
worker.join();start() просит JVM создать новый путь исполнения, который затем вызовет run(). Для обычного platform thread прямой вызов worker.run() — вызов метода в текущем потоке, а не запуск. У virtual thread (Java 21+) прямой вызов run() вообще не исполняет переданную задачу: пользоваться нужно start().
Один объект Thread запускают только один раз. Повторный start() завершится IllegalThreadStateException.
Наследование оправдано редко:
final class LoggingThread extends Thread {
@Override
public void run() {
// задача
}
}Оно связывает задачу с инфраструктурой, занимает единственное наследование класса и усложняет повторное использование.
Задача отдельно от исполнителя
Runnable cleanup = () -> clearExpiredEntries();
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.execute(cleanup);
} finally {
executor.shutdown();
}Для серверного кода это обычный выбор: пул ограничивает число потоков, переиспользует их и принимает политику очереди. Если нужен результат или checked exception, используется Callable.
Что часто путают
start()не гарантирует, что новый поток начнет работу раньше следующей строки текущего потока.- Объект
Runnableсам по себе не поток: один экземпляр можно выполнить разными исполнителями. Threadнельзя безопасно остановить извне; нужен протокол прерывания — Остановка потока.- «Полный контроль через наследование
Thread» — не преимущество: все методы доступны у объектаThreadи при композиции.
На собеседовании
- Чем
start()отличается отrun()?start()создает новый путь исполнения;run()выполняется как обычный метод. - Можно ли запустить один
Threadдважды? Нет. - Почему обычно выбирают
Runnable? Он разделяет задачу и способ исполнения. - Когда нужен
Callable? Когда задача возвращает результат или бросает checked exception. - Нужно ли создавать поток на каждую короткую задачу? Обычно нет, используют ограниченный executor.