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

Ответ

Thread — это класс, который представляет собой поток выполнения. Runnable — это функциональный интерфейс, задача (run()), которую может выполнить поток.

Ключевые отличия:

Аспект Thread (класс) Runnable (интерфейс)
Наследование Наследование от Thread занимает слот единственного родительского класса. Реализация Runnable не мешает наследовать другой класс.
Разделение ответственности Совмещает в себе и задачу (код в run()), и механизм выполнения (поток). Определяет только задачу. Управление потоком — отдельно.
Переиспользование Задача привязана к конкретному экземпляру Thread. Один экземпляр Runnable можно выполнить в разных потоках (Thread, пулы).
Рекомендация Наследовать Thread обычно не рекомендуется. Предпочтительный подход согласно best practices.

Пример реализации через Runnable (рекомендуется):

// Задача отделена от механизма выполнения
Runnable task = () -> {
    System.out.println("Выполняется в потоке: " + Thread.currentThread().getName());
};

// Запуск задачи в новом потоке
Thread thread = new Thread(task, "MyThread-1");
thread.start();

// Та же задача может быть отправлена в ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(task);

Пример наследования Thread (не рекомендуется):

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Выполняется в: " + getName());
    }
}
new MyThread().start(); // Задача и поток неразделимы.

Вывод: Всегда предпочитайте Runnable (или Callable) для определения задачи. Это обеспечивает лучшую архитектуру, гибкость и совместимость с современными concurrency API (ExecutorService, ForkJoinPool).

Ответ 18+ 🔞

Слушай, а вот эта вся хуйня про Thread и Runnable — это ж классика, блядь, как Муму с Герасимом! Один немой и сильный, а другой — просто задача, которую можно в разные места тыкать.

Так вот, представляешь: Thread — это, блядь, сам Герасим. Здоровый такой, конкретный чувак, который может что-то делать. А Runnable — это его команда «Муму!», то есть сама задача, которую он выполняет. Герасим может орать «Муму!» и топить собаку, а может, если захочет, орать «Муму!» и поливать огород, понимаешь? Задача одна, а применений — овердохуища.

Ключевые отличия, на которых всё ебётся:

Штука Thread (класс, типа Герасим) Runnable (интерфейс, типа команда «Муму!»)
Наследование Если ты наследуешься от Thread — всё, пидарас шерстяной, ты занял единственное место для родителя. Больше ни от кого не унаследуешь, как Герасим, который только немой и всё. А Runnable — это так, интерфейс, хуй с горы. Реализовал его и дальше наследуй что хочешь — хоть от слона, хоть от чайника.
Разделение ответственности Thread — это и есть сам поток, и задача в нём. Всё в одной куче, как Герасим, который и силач, и дворник, и собачник. Runnable — это только задача, код в методе run(). А кто её будет выполнять — отдельный вопрос. Можно хоть в Thread запихнуть, хоть в пул потоков. Гибкость, блядь!
Переиспользование Создал MyThread — и он привязан к конкретному потоку. Как Герасим, который только своё дело и делает. А один Runnable можно, сука, запустить в десяти потоках! Одна задача — много исполнителей. Экономия, мать его!
Что говорят умные Наследовать Thread — это старый, кривой способ. Так уже не пишут, если ты не хочешь выглядеть как чувак из 90-х. Runnable — это канон, блядь. Все best practices кричат: «Используй Runnable, ёпта!»

Вот как это выглядит в коде, если делать по-уму (через Runnable):

// Это задача — просто код, который надо выполнить. Как команда "Муму!"
Runnable task = () -> {
    System.out.println("Выполняется в потоке: " + Thread.currentThread().getName());
};

// Кидаем эту задачу в новый поток (создаём Герасима и говорим ему "Муму!")
Thread thread = new Thread(task, "MyThread-1");
thread.start();

// А можно ту же самую задачу отдать в пул потоков (целой бригаде Герасимов)
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(task); // И пошло-поехало!

А вот как делать НЕ НАДО (наследование Thread):

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Выполняется в: " + getName());
    }
}
new MyThread().start(); // Всё, приехали. Задача и поток — одно целое. Не оторвёшь.

Итог, блядь: всегда юзай Runnable (или его брата Callable, если нужно результат вернуть). Это разделяет задачу и исполнителя, даёт гибкость и позволяет использовать современные штуки типа ExecutorService. А Thread наследовать — это как Герасиму пытаться объяснить, что собаку топить не надо. Бесполезно и архаично. В рот меня чих-пых, вот и вся философия!