← Назад к вопросам

В чем разница между Thread и Runnable?

2.0 Middle🔥 171 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

В чем разница между Thread и Runnable

Это фундаментальный вопрос о многопоточности в Java. Давай разберёмся в различиях, преимуществах и когда использовать что.

Основная разница

Thread — это класс, который представляет сам поток выполнения. Runnable — это интерфейс, который определяет задачу для выполнения в потоке.

// Thread — класс
public class MyThread extends Thread {
    public void run() {
        System.out.println("Выполняется в потоке");
    }
}

// Runnable — интерфейс
public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Выполняется в потоке");
    }
}

Способ создания

Вариант 1: Extends 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();  // ВАЖНО: start(), не run()
    }
}

Вариант 2: Implements Runnable

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Поток: " + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();  // Запускаем поток
    }
}

Вариант 3: Lambda (Java 8+)

public class LambdaThread {
    public static void main(String[] args) {
        // Runnable это функциональный интерфейс
        Thread thread = new Thread(() -> {
            System.out.println("Поток: " + Thread.currentThread().getName());
        });
        thread.start();
    }
}

Ключевые различия

КритерийThreadRunnable
ТипКлассИнтерфейс
Наследованиеextends Threadimplements Runnable
Множественное наследованиеНЕТ (один класс)ДА (несколько интерфейсов)
Состояние потокаЕсть (active state)НЕТ (это просто задача)
Методы потокаThread.sleep(), interrupt()Нет прямого доступа
ГибкостьНизкаяВысокая
РекомендуетсяРедкоДА (preferred way)

Thread: состояние и методы

public class ThreadStateExample extends Thread {
    @Override
    public void run() {
        System.out.println("Выполняется");
    }
    
    public static void main(String[] args) throws InterruptedException {
        ThreadStateExample thread = new ThreadStateExample();
        
        // Методы доступны только для потока
        thread.setName("MyThread");           // имя потока
        thread.setPriority(Thread.MAX_PRIORITY); // приоритет (1-10)
        thread.setDaemon(true);              // daemon flag
        
        thread.start();  // NEW -> RUNNABLE -> RUNNING -> TERMINATED
        
        // Получить информацию о потоке
        System.out.println("ID: " + thread.getId());
        System.out.println("State: " + thread.getState());
        System.out.println("Alive: " + thread.isAlive());
        
        thread.join();  // ждать завершения потока
    }
}

Runnable: просто задача

public class RunnableTaskExample implements Runnable {
    @Override
    public void run() {
        System.out.println("Выполняется");
    }
    
    public static void main(String[] args) {
        Runnable task = new RunnableTaskExample();
        
        // Runnable — это просто задача, без управления потоком
        // Можно запустить в разных потоках
        
        Thread thread1 = new Thread(task, "Thread-1");
        Thread thread2 = new Thread(task, "Thread-2");
        
        thread1.start();
        thread2.start();
        // Один и тот же Runnable в двух разных потоках
    }
}

Почему Runnable лучше: наследование

// ПРОБЛЕМА с extends Thread: нельзя наследовать от другого класса
public class Worker extends Thread {
    // extends Thread
}

public class Worker extends SomeOtherClass implements Runnable {
    // РАБОТАЕТ: можем наследовать от SomeOtherClass и использовать Runnable
    @Override
    public void run() {
        // выполнение
    }
}

Это основная причина предпочитать Runnable: в Java одно наследование, и если класс уже наследует что-то, он не может наследовать Thread.

Важное различие: run() vs start()

public class RunVsStart {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Поток: " + Thread.currentThread().getName());
        });
        
        // НЕПРАВИЛЬНО: run() выполнится в MAIN потоке
        thread.run();
        // Вывод: Поток: main
        
        // ПРАВИЛЬНО: start() создаёт НОВЫЙ поток
        thread.start();
        // Вывод: Поток: Thread-0
    }
}

Использование с ExecutorService (modern approach)

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // ExecutorService управляет потоками
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // Runnable 
        Runnable task1 = () -> System.out.println("Task 1");
        Runnable task2 = () -> System.out.println("Task 2");
        
        executor.execute(task1);
        executor.execute(task2);
        
        // Callable (как Runnable, но с возвращаемым значением)
        Callable<Integer> task3 = () -> {
            System.out.println("Task 3");
            return 42;
        };
        
        Future<Integer> future = executor.submit(task3);
        int result = future.get();  // 42
        
        executor.shutdown();
    }
}

Сравнение подходов

1. Extends Thread (устаревший подход)

public class OldWay extends Thread {
    @Override
    public void run() {
        System.out.println("Old way");
    }
    
    public static void main(String[] args) {
        new OldWay().start();
    }
}

Минусы:

  • Нельзя наследовать другой класс
  • Всё перемешано (логика + управление потоком)

2. Implements Runnable (лучше)

public class BetterWay implements Runnable {
    @Override
    public void run() {
        System.out.println("Better way");
    }
    
    public static void main(String[] args) {
        new Thread(new BetterWay()).start();
    }
}

Преимущества:

  • Можно наследовать другой класс
  • Разделение ответственности

3. Lambda/ExecutorService (современный подход)

public class ModernWay {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        executor.execute(() -> System.out.println("Modern way"));
        
        executor.shutdown();
    }
}

Преимущества:

  • Чистый код
  • Управление потоками через ExecutorService
  • Лучше для масштабирования

Практический пример: многопоточное скачивание

public class DownloadManager {
    // Runnable задача для скачивания
    static class DownloadTask implements Runnable {
        private String url;
        private String filename;
        
        public DownloadTask(String url, String filename) {
            this.url = url;
            this.filename = filename;
        }
        
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + 
                " скачивает " + filename);
            try {
                Thread.sleep(2000);  // simulate download
                System.out.println(Thread.currentThread().getName() + 
                    " завершил " + filename);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // Пул из 3 потоков
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // Создаём задачи (Runnable)
        executor.execute(new DownloadTask("url1", "file1.zip"));
        executor.execute(new DownloadTask("url2", "file2.zip"));
        executor.execute(new DownloadTask("url3", "file3.zip"));
        executor.execute(new DownloadTask("url4", "file4.zip"));
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    }
}

// Вывод:
// pool-1-thread-1 скачивает file1.zip
// pool-1-thread-2 скачивает file2.zip
// pool-1-thread-3 скачивает file3.zip
// pool-1-thread-1 завершил file1.zip
// pool-1-thread-1 скачивает file4.zip
// ...

Когда использовать что

Используй Runnable (или Lambda) когда:

  • Нужна логика для выполнения в потоке (99% случаев)
  • Класс уже наследует другой класс
  • Хочешь использовать ExecutorService
  • Собираешься использовать тот же код в разных потоках

Используй extends Thread когда:

  • Нужно переопределить другие методы Thread (редко)
  • Пишешь учебный код для понимания

Вывод

АспектThreadRunnable
Что это?Класс (сам поток)Интерфейс (задача)
НаследованиеОдин класс (extends)Несколько (implements)
Используй в 99% случаевНЕТДА
С ExecutorServiceСложнееЕстественно
Современный подходНетДА (Lambda + Executor)

Правило: Runnable для логики, Thread для управления потоком. Отделяй ответственность.