«Реализует Runnable» против «расширяет поток» в Java

2119

Из того, сколько времени я провел с потоками в Java, я нашел два способа написания потоков:

С implements Runnable:

public class MyRunnable implements Runnable {
    public void run() {
        //Code
    }
}
//Started with a "new Thread(new MyRunnable()).start()" call

Или с extends Thread:

public class MyThread extends Thread {
    public MyThread() {
        super("MyThread");
    }
    public void run() {
        //Code
    }
}
//Started with a "new MyThread().start()" call

Есть ли существенная разница в этих двух блоках кода?

user65374
источник
57
Спасибо за этот вопрос, ответы прояснили многие заблуждения, которые у меня были. Я искал правильный способ сделать потоки Java до появления SO, и там было много дезинформации / устаревшей информации.
Джеймс МакМэхон
5
есть одна причина, по которой вы можете захотеть расширить Thread ( но я не рекомендую это ), вы можете превентивно обрабатывать interrupt(). Опять же, это идея, это может быть полезно в правильном случае, однако я не рекомендую это.
bestsss
Пожалуйста, смотрите также хорошо объясненный ответ: stackoverflow.com/q/5562720/285594
@bestsss, я пытаюсь разобраться, что ты можешь сказать об обработке прерываний (). Вы пытаетесь переопределить метод?
Боб Кросс
8
да. Согласно коду, класс Thread A может расширять любой класс, в то время как класс Thread B не может расширять любой другой класс
Мани Дипак

Ответы:

1674

Да Runnable, ИМО - предпочтительный способ сделать это. Вы на самом деле не специализируете поведение потока. Вы просто даете ему что-то, чтобы бежать. Это означает, что композиция - это философски более чистый путь.

В практическом плане это означает, что вы можете реализовывать Runnableи расширять другие классы.

Джон Скит
источник
151
Точно, хорошо поставлено. Какое поведение мы пытаемся переписать в Thread, расширяя его? Я бы сказал, что большинство людей не пытаются переписать какое-либо поведение, а пытаются использовать поведение Thread.
hooknc
87
В качестве дополнительного комментария: если вы создаете экземпляр Thread и не вызываете его метод start (), вы создаете утечку памяти в Java <5 (этого не происходит с Runnables): stackoverflow.com/questions/107823/…
Nacho Coloma
25
Одним из незначительных преимуществ Runnable является то, что, если в определенных обстоятельствах вас не волнует или вы не хотите использовать многопоточность и вы просто хотите выполнить код, у вас есть возможность просто вызвать run (). например (очень волнистый) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
user949300
10
@ user949300, вы также можете сделать это с помощью extends Threadи, если вы не хотите многопоточности, зачем вам вообще это реализовывать Runnable...
m0skit0
30
Перефразируя Сьерру и Бейтса, ключевым преимуществом реализации Runnable является то, что вы архитектурно отделяете «работу» от «бегуна».
8bitjunkie
569

tl; dr: реализует Runnable лучше. Тем не менее, предостережение важно

В общем, я бы рекомендовал использовать что-то подобное, Runnableа не Threadпотому, что это позволяет вам сохранить свою работу только слабо связанной с вашим выбором параллелизма. Например, если вы используете a Runnableи позже решите, что это на самом деле не требует своего Thread, вы можете просто вызвать threadA.run ().

Предостережение: где-то здесь я настоятельно не рекомендую использовать сырые темы. Я очень предпочитаю использовать Callables и FutureTasks (из javadoc: « отменяемые асинхронные вычисления»). Интеграция тайм-аутов, правильная отмена и объединение потоков в современной поддержке параллелизма гораздо более полезны для меня, чем груды необработанных потоков.

Продолжение: есть FutureTaskконструктор, который позволяет вам использовать Runnables (если это то, с чем вам удобнее всего) и при этом пользоваться преимуществами современных инструментов параллелизма. Процитирую Javadoc :

Если вам не нужен конкретный результат, рассмотрите возможность использования конструкций вида:

Future<?> f = new FutureTask<Object>(runnable, null)

Итак, если мы заменим их runnableна ваши threadA, мы получим следующее:

new FutureTask<Object>(threadA, null)

Другой вариант, который позволяет вам оставаться ближе к Runnables, - ThreadPoolExecutor . Вы можете использовать метод execute для передачи Runnable для выполнения «заданной задачи когда-нибудь в будущем».

Если вы хотите попробовать использовать пул потоков, приведенный выше фрагмент кода будет выглядеть примерно так (с использованием фабричного метода Executors.newCachedThreadPool () ):

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());
Боб Кросс
источник
40
Это лучше, чем принятый ответ ИМХО. Одна вещь: фрагмент кода, который у вас есть, не закрывает исполнителя, и я вижу миллионы вопросов, где люди ошибаются, создавая нового исполнителя каждый раз, когда они хотят создать задачу. esбыло бы лучше в качестве статического (или внедренного) поля, чтобы оно создавалось только один раз.
artbristol
8
@artbristol, спасибо! Я не согласен с новым исполнителем (мы делаем то, что вы предлагаете в нашем коде). При написании исходного ответа я пытался написать минимальный код, аналогичный исходному фрагменту. Мы должны надеяться, что многие читатели этих ответов используют их как трамплин. Я не пытаюсь написать замену для Javadoc. Я эффективно пишу маркетинговые материалы для этого: если вам нравится этот метод, вы должны увидеть все другие замечательные вещи, которые мы можем предложить ...!
Боб Кросс
3
Я знаю, что немного опаздываю, комментируя это, но общение FutureTaskнапрямую - это совсем не то, что вы хотите сделать. ExecutorServices создаст соответствующую Futureдля вас , когда вы / им. Аналогично для с и когда вы / . submitRunnableCallableScheduledExecutorServiceScheduledFuturescheduleRunnableCallable
Powerlord
3
@Powerlord, я намеревался сделать фрагменты кода, которые соответствуют ОП как можно ближе. Я согласен, что новый FutureTask не является оптимальным, но он понятен для целей объяснения.
Боб Кросс
258

Мораль истории:

Наследовать, только если вы хотите переопределить какое-либо поведение.

Или, скорее, это следует читать как:

Унаследуй меньше, интерфейс больше.

Panzerschreck
источник
1
Это всегда должен быть вопрос, если вы начнете делать параллельный запуск Object! Вам даже нужны функции Thread Object?
Либерти
4
При наследовании от Thread почти всегда требуется переопределить поведение run()метода.
Уоррен Дью
1
Вы не можете переопределить поведение a java.lang.Thread, переопределив run()метод. В этом случае вам нужно переопределить start()метод, я думаю. Обычно вы просто повторно используете поведение java.lang.Thread, вводя свой блок выполнения в run()метод.
sura2k
Наследование предназначено не только для переопределения какого-либо поведения, но и для использования общего поведения. И наоборот, чем больше переопределений, тем хуже иерархия.
Пежа
232

Ну так много хороших ответов, я хочу добавить еще об этом. Это поможет понять Extending v/s Implementing Thread.
Extends очень тесно связывает два файла классов и может привести к некоторым трудностям при работе с кодом.

Оба подхода выполняют одну и ту же работу, но есть некоторые различия.
Наиболее распространенная разница

  1. Когда вы расширяете класс Thread, после этого вы не можете расширять любой другой класс, который вам требуется. (Как вы знаете, Java не позволяет наследовать более одного класса).
  2. Когда вы реализуете Runnable, вы можете сэкономить место для вашего класса для расширения любого другого класса в будущем или сейчас.

Однако одно существенное различие между реализацией Runnable и расширением Thread заключается в том, что
by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instance.

Следующий пример поможет вам лучше понять

//Implement Runnable Interface...
 class ImplementsRunnable implements Runnable {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ImplementsRunnable : Counter : " + counter);
 }
}

//Extend Thread class...
class ExtendsThread extends Thread {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ExtendsThread : Counter : " + counter);
 }
}

//Use the above classes here in main to understand the differences more clearly...
public class ThreadVsRunnable {

public static void main(String args[]) throws Exception {
    // Multiple threads share the same object.
    ImplementsRunnable rc = new ImplementsRunnable();
    Thread t1 = new Thread(rc);
    t1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t2 = new Thread(rc);
    t2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t3 = new Thread(rc);
    t3.start();

    // Creating new instance for every thread access.
    ExtendsThread tc1 = new ExtendsThread();
    tc1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc2 = new ExtendsThread();
    tc2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc3 = new ExtendsThread();
    tc3.start();
 }
}

Вывод вышеуказанной программы.

ImplementsRunnable : Counter : 1
ImplementsRunnable : Counter : 2
ImplementsRunnable : Counter : 3
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1

В подходе интерфейса Runnable создается только один экземпляр класса, и он используется разными потоками. Таким образом, значение счетчика увеличивается для каждого доступа к потоку.

Принимая во внимание, что подход класса Thread, вы должны создать отдельный экземпляр для каждого доступа потока. Следовательно, для каждого экземпляра класса выделяется разная память, и у каждого есть отдельный счетчик, значение остается тем же, что означает, что никакого приращения не произойдет, потому что ни одна из ссылок на объект не является одинаковой.

Когда использовать Runnable?
Используйте интерфейс Runnable, когда вы хотите получить доступ к тем же ресурсам из группы потоков. Избегайте использования класса Thread здесь, потому что создание нескольких объектов потребляет больше памяти, и это приводит к значительному снижению производительности.

Класс, который реализует Runnable, не является потоком, а просто классом. Чтобы Runnable стал потоком, вам нужно создать экземпляр потока и передать себя в качестве цели.

В большинстве случаев интерфейс Runnable следует использовать, если вы планируете только переопределить run()метод, а не другие методы Thread. Это важно, потому что классы не должны быть разделены на подклассы, если программист не намеревается изменить или улучшить фундаментальное поведение класса.

Когда необходимо расширить суперкласс, реализация интерфейса Runnable более подходит, чем использование класса Thread. Потому что мы можем расширить другой класс при реализации интерфейса Runnable для создания потока.

Я надеюсь, это поможет!

Рупеш Ядав
источник
40
Ваш код явно неверен. Я имею в виду, он делает то, что делает, но не то, что вы намеревались показать.
Зеро
38
Для пояснения: для случая запуска вы использовали один и тот же экземпляр ImplementsRunnable для запуска нескольких потоков, тогда как для случая потока вы создаете разные экземпляры ExtendsThread, что, очевидно, приводит к поведению, которое вы показали. 2-я половина вашего основного метода должна быть: ExtendsThread et = new ExtendsThread(); Thread tc1 = new Thread(et); tc1.start(); Thread.sleep(1000); Thread tc2 = new Thread(et); tc2.start(); Thread.sleep(1000); Thread tc3 = new Thread(et); tc3.start(); это яснее?
Зеро
19
Я пока не понимаю ваших намерений, но я хотел сказать, что если вы создадите несколько экземпляров ExtendsThread - все они вернут 1 (как вы показали). Вы можете получить те же результаты для Runnable, выполнив там то же самое, то есть создав несколько экземпляров ImplementsRunnable.
Зеро
3
@zEro Привет, я из будущего. Учитывая, что ваша версия кода также имеет Threadинкрементное значение, является ли утверждение by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instanceневерным? Если нет, то каков случай, который демонстрирует это?
Evil Стиральная машина
4
Код, размещенный здесь, вводит в заблуждение и может привести к головной боли. Спасибо @zEro за небольшую очистку.
Никос
78

Одна вещь, которую я удивляюсь, еще не упоминалась, это то, что реализация Runnableделает ваш класс более гибким.

Если вы расширяете поток, то действие, которое вы делаете, всегда будет в потоке. Тем не менее, если вы реализуетеRunnable это не должно быть. Вы можете запустить его в потоке, или передать его какому-либо сервису-исполнителю, или просто передать как задачу в рамках одного многопоточного приложения (возможно, для запуска в более позднее время, но в том же потоке). Варианты намного более открыты, если вы просто используете, Runnableчем если вы привязываете себя Thread.

Herms
источник
6
Ну, на самом деле вы тоже можете делать то же самое с Threadобъектом, потому что Thread implements Runnable… ;-) Но это приятнее делать это с a, Runnableчем делать с a Thread!
siegi
8
Правда, но Threadдобавляет много лишних вещей, которые вам не нужны, а во многих случаях не нужны. Вам всегда лучше реализовывать интерфейс, который соответствует тому, что вы на самом деле делаете.
Herms
74

Если вы хотите реализовать или Runnableрасширить любой другой класс, тогда интерфейс является наиболее предпочтительным, в противном случае, если вы не хотите, чтобы какой-либо другой класс расширял или реализовывал, тогда Threadкласс предпочтительнее.

Наиболее распространенная разница

введите описание изображения здесь

Когда ты extends Thread занимаетесь, после этого вы не можете расширить любой другой класс, который вам требуется. (Как вы знаете, Java не позволяет наследовать более одного класса).

Когда вы implements Runnable, вы можете сэкономить место для вашего класса, чтобы расширить любой другой класс в будущем или сейчас.

  • Java не поддерживает множественное наследование, что означает, что вы можете расширять только один класс в Java, поэтому, расширив класс Thread, вы потеряете свой шанс и не сможете расширять или наследовать другой класс в Java.

  • В объектно-ориентированном программировании расширение класса обычно означает добавление новых функций и изменение или улучшение поведения. Если мы не вносим никаких изменений в Thread, используйте вместо этого интерфейс Runnable.

  • Runnable интерфейс представляет собой задачу, которая может быть выполнена либо обычным потоком, либо исполнителями, либо любым другим способом. так что логическое разделение Task как Runnable чем Thread является хорошим дизайнерским решением.

  • Разделение задачи как Runnable означает, что мы можем повторно использовать задачу, а также имеем возможность выполнять ее различными способами. так как вы не можете перезапустить поток после его завершения. снова Runnable vs Thread для задачи, Runnable - победитель.

  • Java-дизайнер признает это, и поэтому исполнители принимают Runnable как Task, и у них есть рабочий поток, который выполняет эту задачу.

  • Наследование всех методов Thread - это дополнительные издержки только для представления Задачи, которые можно легко выполнить с помощью Runnable.

Предоставлено javarevisited.blogspot.com

Это были некоторые заметные различия между Thread и Runnable в Java. Если вы знаете какие-либо отличия между Thread и Runnable, пожалуйста, поделитесь им в комментариях. Я лично использую Runnable over Thread для этого сценария и рекомендую использовать интерфейс Runnable или Callable в зависимости от ваших требований.

Однако существенная разница есть.

Когда вы extends Threadзанимаетесь классом, каждый ваш поток создает уникальный объект и связывается с ним. Когда вы implements Runnable, он разделяет один и тот же объект на несколько потоков.

Нидхиш Кришнан
источник
73

На самом деле, не стоит сравнивать Runnableи Threadдруг с другом.

Эти два имеют зависимость и отношения в многопоточности так же, как Wheel and Engineотношения автомобиля.

Я бы сказал, что есть только один способ многопоточности с двумя шагами. Позвольте мне высказать свое мнение.

Runnable:
при реализации interface Runnableэто означает, что вы создаете что-то, что находится run ableв другом потоке. Теперь создание чего-либо, что может выполняться внутри потока (может выполняться внутри потока), не означает создание потока.
Таким образом, класс MyRunnable- это не что иное, как обычный класс с void runметодом. И это объекты будут обычными объектами только с методом, runкоторый будет нормально выполняться при вызове. (если мы не передадим объект в потоке).

Thread:
class Thread я бы сказал, очень специальный класс с возможностью запуска нового потока, который фактически позволяет многопоточность через его start()метод.

Почему не стоит сравнивать?
Потому что нам нужны они оба для многопоточности.

Для многопоточности нам нужны две вещи:

  • То, что может работать внутри потока (Runnable).
  • Что-то, что может начать новую тему (Thread).

Так что технически и теоретически оба они необходимы для запуска потока, один будет запущен, а другой заставит его работать (какWheel and Engine в автомобиле).

Вот почему вы не можете запустить поток, MyRunnableвам нужно передать его экземпляру Thread.

Но можно создать и запустить поток только с использованием, class Threadпотому что Class Threadреализует, Runnableтак что мы все знаем, Threadтакже является Runnableвнутренним.

Наконец Threadи Runnableдополняют друг друга для многопоточности не конкурента или замены.

Саиф
источник
3
Точно! Это должен быть принятый ответ. Кстати, я думаю, что вопрос был отредактирован и ThreadAбольше не имеет смысла
idelvall
принятый ответ намного больше делегат спасибо за ваш ответ @idelvall
Саиф
Лучший ответ! Спасибо!
MichaelYe
44

Вы должны реализовать Runnable, но если вы работаете на Java 5 или выше, вы не должны запускать его с, new Threadа использовать вместо этого ExecutorService . Подробнее см .: Как реализовать простую многопоточность в Java .

Фабиан Стиг
источник
5
Я не думаю, что ExecutorService будет настолько полезен, если вы просто захотите запустить один поток.
Powerlord
1
Из того, что я узнал, больше не нужно вообще запускать поток самостоятельно, потому что предоставление этого службе исполнителю делает все намного более управляемым (например, ожидание приостановки потока). Кроме того, я не вижу ничего в этом вопросе, что подразумевает, что речь идет об одной теме.
Фабиан Стиг
4
Какой смысл использовать любую многопоточность, если мы знаем, что это будет одна нить. Итак, давайте предположим, что у нас есть несколько потоков, и этот ответ является ценным.
zEro
1
@zEro Я уверен, что есть причина, по которой существует только одна ветка рассылки событий. Я сомневаюсь, что это единственный случай, когда лучше иметь отдельный поток, но, возможно, не лучше иметь несколько.
porcoesphino
33

Я не эксперт, но я могу придумать одну причину для реализации Runnable вместо расширения Thread: Java поддерживает только одиночное наследование, поэтому вы можете расширять только один класс.

Редактировать: первоначально говорилось: «Реализация интерфейса требует меньше ресурсов». также, но вам нужно создать новый экземпляр Thread, так что это было неправильно.

Powerlord
источник
В runnable мы не можем делать сетевые вызовы, не так ли? Поскольку у меня есть android.os.NetworkOnMainThreadException. Но с помощью потока я могу делать сетевые вызовы. Пожалуйста, поправьте меня, если я ошибаюсь.
Набиль Тобани,
@NabeelThobani Обычная Java не заботится, но звучит так, как Android. Я не достаточно знаком с Android, чтобы сказать, хотя.
Powerlord
1
@NabeelThobani Конечно, вы можете. Возможно, вы не создаете тему с помощью Runnable.
m0skit0
20

Я бы сказал, что есть третий путь:

public class Something {

    public void justAnotherMethod() { ... }

}

new Thread(new Runnable() {
   public void run() {
    instanceOfSomething.justAnotherMethod();
   }
}).start();

Возможно, на это немного повлияло мое недавнее интенсивное использование Javascript и Actionscript 3, но в этом случае вашему классу не нужно реализовывать довольно расплывчатый интерфейс, как Runnable.

Барт ван Хейкелом
источник
38
Это не совсем третий путь. Вы все еще используете Runnable, просто делаете это анонимно.
Дон Роби
2
@ Дон Роби: Который отличается. Это часто удобно, и вы можете использовать поля и конечные локальные переменные из содержащего класса / метода.
Барт ван Хейкелом
6
@BartvanHeukelom Это удобно, но не отличается. Вы можете сделать это с любым типом вложенного класса, то есть внутренними классами, локальными классами и лямбда-выражениями.
xehpuk
18

С выпуском Java 8 появился третий вариант.

Runnable это функциональным интерфейсом , что означает, что его экземпляры могут быть созданы с помощью лямбда-выражений или ссылок на методы.

Ваш пример можно заменить на:

new Thread(() -> { /* Code here */ }).start()

или если вы хотите использовать ExecutorServiceи ссылку на метод:

executor.execute(runner::run)

Они не только намного короче ваших примеров, но также обладают многими преимуществами, указанными в других ответах на использование Runnableover Thread, такими как одиночная ответственность и использование композиции, потому что вы не специализируете поведение потока. Этот способ также позволяет избежать создания дополнительного класса, если все, что вам нужно, это то, Runnableчто вы делаете в своих примерах.

Alex
источник
Этот ответ требует объяснения. После некоторого недоумения я заключаю, что () -> {}должен представлять собой пользовательскую логику, которая кому-то нужна? Так было бы лучше сказать как () -> { /* Code here */ }?
ToolmakerSteve
17

Создание интерфейса дает более четкое разделение между вашим кодом и реализацией потоков, поэтому я бы предпочел реализовать Runnable в этом случае.

starblue
источник
12

Кажется, все здесь думают, что реализация Runnable - это путь, и я на самом деле не согласен с ними, но, на мой взгляд, есть также причина для расширения Thread, фактически вы как бы продемонстрировали это в своем коде.

Если вы реализуете Runnable, то класс, реализующий Runnable, не будет контролировать имя потока, это вызывающий код, который может установить имя потока, например, так:

new Thread(myRunnable,"WhateverNameiFeelLike");

но если вы расширяете Thread, тогда вы можете управлять этим внутри самого класса (как в вашем примере вы называете поток «ThreadB»). В этом случае вы:

А) может дать ему более полезное имя для целей отладки

B) принуждают использовать это имя для всех экземпляров этого класса (если вы не игнорируете тот факт, что он является потоком, и делаете вышеизложенное с ним, как будто это Runnable, но мы говорим о соглашении здесь в любом случае, так что можно игнорировать эту возможность я чувствую).

Вы можете даже, например, взять трассировку стека его создания и использовать его в качестве имени потока. Это может показаться странным, но в зависимости от структуры вашего кода это может быть очень полезно для целей отладки.

Это может показаться мелочью, но когда у вас очень сложное приложение с большим количеством потоков и все внезапно «остановилось» (либо по причине тупиковой ситуации, либо, возможно, из-за недостатка сетевого протокола, который был бы меньше очевидно - или по другим бесконечным причинам) затем получение дампа стека из Java, где все потоки называются «Thread-1», «Thread-2», «Thread-3», не всегда очень полезно (это зависит от того, как ваши потоки структурирован и можете ли вы с пользой определить, что есть что, просто по их трассировке стека - не всегда возможно, если вы используете группы из нескольких потоков, все выполняющие один и тот же код).

Сказав, что вы, конечно, также можете сделать вышеупомянутое в общем виде, создав расширение класса потока, которое устанавливает его имя в виде трассировки стека его вызова создания, а затем используйте это с вашими реализациями Runnable вместо стандартного класса потока Java. (см. ниже), но в дополнение к трассировке стека может быть более специфичная для контекста информация, которая будет полезна в имени потока для отладки (ссылка на одну из множества очередей или сокетов, которые он может обработать, например, в этом случае вы можете предпочесть расширить Thread специально для этого случая, чтобы вы могли заставить компилятор заставить вас (или других пользователей, использующих ваши библиотеки) передавать определенную информацию (например, нужную очередь / сокет) для использования в имени).

Вот пример общего потока с трассировкой вызывающего стека в качестве имени:

public class DebuggableThread extends Thread {
    private static String getStackTrace(String name) {
        Throwable t= new Throwable("DebuggableThread-"+name);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(os);
        t.printStackTrace(ps);
        return os.toString();
    }

    public DebuggableThread(String name) {
        super(getStackTrace(name));
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new Thread());
        System.out.println(new DebuggableThread("MainTest"));
    }
}

и вот пример выходных данных, сравнивающих два имени:

Thread[Thread-1,5,main]
Thread[java.lang.Throwable: DebuggableThread-MainTest
    at DebuggableThread.getStackTrace(DebuggableThread.java:6)
    at DebuggableThread.<init>(DebuggableThread.java:14)
    at DebuggableThread.main(DebuggableThread.java:19)
,5,main]
Антоним
источник
cHao в чем твоя точка зрения? Вы не можете использовать свой код выше во время выполнения потока, чтобы получить трассировку стека создания потоков (вместо этого вы получите простое имя или, в лучшем случае, трассировку стека запуска потоков), но с помощью подкласса потока вы можете сделать именно это и заставить это, даже требуя дополнительной информации, специфичной для контекста, что дает вам более конкретное понимание того, в какой именно теме может возникнуть проблема.
AntonyM
8
Моя точка зрения такова: «Если вы реализуете Runnable, то класс, реализующий Runnable, не имеет контроля над именем потока ...» явно ложно. Реализация класса Runnableдействительно может управлять именем потока, поскольку поток, выполняющий код, по определению является текущим потоком (и любой код, который проходит проверки безопасности, имеет контроль над именами потоков). Учитывая, что вы посвятили половину своего поста «боже, как насчет названий тем!», Это кажется чем-то большим.
cHao
Название темы? Ничто не мешает вам расширять класс потока.
RichieHH
11

Runnable потому что:

  • Предоставляет большую гибкость реализации Runnable для расширения другого класса
  • Отделяет код от исполнения
  • Позволяет запускать ваш исполняемый файл из пула потоков, потока событий или любым другим способом в будущем.

Даже если вам не нужно ничего этого сейчас, вы можете в будущем. Поскольку переопределение Thread не дает никаких преимуществ, Runnable является лучшим решением.

n13
источник
11

Поскольку это очень популярная тема, и хорошие ответы распространяются повсеместно и рассматриваются очень глубоко, я чувствовал, что было бы оправданным объединить хорошие ответы от других в более сжатую форму, чтобы у новичков был простой обзор:

  1. Вы обычно расширяете класс для добавления или изменения функциональности. Таким образом, если вы не хотите , чтобы перезаписать любое поведение темы , а затем использовать Runnable.

  2. В том же свете, если вам не нужно , чтобы наследуют методы потоков, можно обойтись и без этого над головой , используя Runnable.

  3. Одиночное наследование : если вы расширяете Thread, вы не можете расширяться из любого другого класса, поэтому, если это то, что вам нужно сделать, вы должны использовать Runnable.

  4. Это хорошая идея, чтобы отделить доменную логику от технических средств, в этом смысле лучше иметь выполняемую задачу, изолирующую вашу задачу от вашего бегуна .

  5. Вы можете выполнить один и тот же объект Runnable несколько раз , однако объект Thread можно запустить только один раз. (Возможно, причина, по которой Исполнители принимают Runnables, но не Threads.)

  6. Если вы разрабатываете свою задачу как Runnable, у вас есть все возможности использовать ее сейчас и в будущем . Вы можете запустить его одновременно через Executors, а также через Thread. И вы все равно можете использовать / вызывать его не одновременно в том же потоке, как любой другой обычный тип / объект.

  7. Это также облегчает разделение задач-логики и аспектов параллелизма в ваших модульных тестах .

  8. Если вас интересует этот вопрос, вас также может заинтересовать разница между Callable и Runnable .

Йорг
источник
@Pino Да, сам поток также является Runnable. Однако, если вы расширяете его, чтобы просто использовать его как Runnable, какой смысл? Почему бы просто не использовать обычный Runnable без всего багажа. Итак, я бы сказал, что если вы расширяете Thread, вы также выполняете его, используя метод start, который можно использовать только один раз. Вот что хотел сказать Нидхиш-Кришнан в своем ответе. Обратите внимание, что мой здесь - это просто компиляция или краткое изложение других ответов.
Йорг
11

Разница между расширением потока и реализацией Runnable:

введите описание изображения здесь

Раман Гупта
источник
8

Это обсуждается в руководстве Oracle « Определение и запуск потока»:

Какой из этих идиом вы должны использовать? Первая идиома, в которой используется объект Runnable, носит более общий характер, поскольку объект Runnable может создавать подкласс класса, отличного от Thread. Второй идиом проще использовать в простых приложениях, но он ограничен тем, что ваш класс задач должен быть потомком Thread. Этот урок фокусируется на первом подходе, который отделяет задачу Runnable от объекта Thread, который выполняет задачу. Этот подход не только более гибок, но и применим к высокоуровневым API-интерфейсам управления потоками, которые будут рассмотрены ниже.

Другими словами, реализация Runnableбудет работать в сценариях, где ваш класс расширяет класс, отличный от Thread. Java не поддерживает множественное наследование. Кроме того, расширение Threadбудет невозможно при использовании некоторых API-интерфейсов управления потоками высокого уровня. Единственный сценарий, в котором расширение Threadявляется предпочтительным, - это небольшое приложение, которое не будет обновляться в будущем. Это почти всегда лучше реализовать, так Runnableкак он становится более гибким по мере роста вашего проекта. Изменение дизайна не будет иметь большого влияния, так как вы можете реализовать много интерфейсов в Java, но расширить только один класс.

Sionnach733
источник
8

Самое простое объяснение состоит в том, что Runnableмы можем назначить один и тот же объект нескольким потокам, и каждый из них Threadимеет одинаковые состояния и поведение объекта.

Например, предположим, что есть два потока, thread1 помещает целое число в массив, а thread2 берет целые числа из массива, когда массив заполняется. Обратите внимание, что для работы thread2 необходимо знать состояние массива, заполнил ли thread1 или нет.

Реализация Runnableпозволяет вам иметь эту гибкость для совместного использования объекта, тогда как extends Threadпозволяет создавать новые объекты для каждого потока, поэтому любое обновление, которое выполняется потоком 1, теряется потоком 2.

Шабабб Карим
источник
7

Если я не ошибаюсь, это более или менее похоже на

В чем разница между интерфейсом и абстрактным классом?

расширяет, устанавливает отношение « Is A », а интерфейс предоставляет возможность « имеет ».

Предпочитаю реализовать Runnable :

  1. Если вам не нужно расширять класс Thread и изменять реализацию по умолчанию Thread API
  2. Если вы выполняете огонь и забыли команду
  3. Если вы уже продлеваете другой класс

Предпочитаю " расширяет тему ":

  1. Если вам нужно переопределить любой из этих методов Thread, как указано на странице документации Oracle

Как правило, вам не нужно переопределять поведение потока. Так реализует Runnable предпочтительнее для большинства случаев.

С другой стороны, использование Advanced ExecutorServiceили ThreadPoolExecutorServiceAPI обеспечивает большую гибкость и контроль.

Посмотрите на этот вопрос SE:

ExecutorService vs Casual Spawner

Равиндра Бабу
источник
5

Отделение класса Thread от реализации Runnable также позволяет избежать потенциальных проблем синхронизации между потоком и методом run (). Отдельный Runnable, как правило, дает большую гибкость в способах ссылки и выполнения исполняемого кода.

Говула Шринивас
источник
5

Это S из SOLID : Single ответственность.

Нить воплощает в себе текущий контекст (как в контексте выполнения: стек кадра, идентификатор потока и т.д.) от асинхронного выполнения куска кода. Этот кусок кода в идеале должен быть одной и той же реализацией, будь то синхронный или асинхронный .

Если вы объедините их в одну реализацию, вы дадите получившемуся объекту две несвязанные причины изменения:

  1. обработка потоков в вашем приложении (т.е. запрос и изменение контекста выполнения)
  2. Алгоритм, реализуемый фрагментом кода (выполняемая часть)

Если используемый вами язык поддерживает частичные классы или множественное наследование, то вы можете отделить каждую причину в своем собственном суперклассе, но он сводится к тому же, что и составление двух объектов, поскольку их наборы функций не перекрываются. Это для теории.

На практике, вообще говоря, программа не должна нести больше сложности, чем необходимо. Если у вас есть один поток, работающий над конкретной задачей, не меняя ее, вероятно, нет смысла делать задачи отдельными классами, и ваш код остается более простым.

В контексте Java , поскольку средство уже существует , вероятно, проще начать непосредственно с автономных Runnableклассов и передавать их экземпляры Thread(или Executor) экземплярам. После того, как вы привыкли к этому шаблону, его не сложнее использовать (или даже читать), чем в случае простого запускаемого потока.

didierc
источник
5

Одна из причин, по которой вы хотите реализовать интерфейс, а не расширять базовый класс, состоит в том, что вы уже расширяете какой-то другой класс. Вы можете расширить только один класс, но вы можете реализовать любое количество интерфейсов.

Если вы расширяете Thread, вы в основном препятствуете выполнению вашей логики любым другим потоком, кроме 'this'. Если вы хотите, чтобы какой-то поток выполнял вашу логику, лучше просто реализовать Runnable.

Нихил А.А.
источник
Да, благодаря реализации интерфейса Runnable, чтобы свободно реализовывать свою собственную логику, расширяя любой класс, поэтому Runnable в основном предпочтительнее, чем класс Thread.
Akash5288
5

если вы используете runnable, вы можете сэкономить место для расширения до любого другого вашего класса.

user2771655
источник
5

Можем ли мы еще раз посетить основную причину, по которой мы хотели, чтобы наш класс вел себя как Thread ? Нет никакой причины, мы просто хотели выполнить задачу, скорее всего, в асинхронном режиме, что точно означает, что выполнение задачи должно происходить из нашего основного потока и основного потока, если он заканчивается раньше, может ждать или не ждать для разветвленной дорожки (задача).

Если в этом вся цель, то где я вижу необходимость в специализированной теме? Это можно сделать, выбрав поток RAW из пула потоков системы и назначив ему нашу задачу (может быть, экземпляром нашего класса), и это все.

Итак, давайте подчинимся концепции ООП и напишем класс нужного нам типа. Есть много способов сделать что-то, правильное решение имеет значение.

Нам нужна задача, поэтому напишите определение задачи, которое можно запустить в потоке. Так что используйте Runnable.

Всегда помните implements, специально используется для передачи поведения и extendsиспользуется для передачи свойства / свойства.

Нам не нужно свойство потока, вместо этого мы хотим, чтобы наш класс вел себя как задача, которую можно запустить.

дхарам
источник
4

Да, если вы вызываете вызов ThreadA, то нет необходимости вызывать метод start, а метод run вызывается только после вызова класса ThreadA. Но если использовать вызов ThreadB, то необходимо вызвать начальный поток для вызова метода run. Если вам нужна помощь, ответьте мне.

Манодж Кумар
источник
4

Я считаю, что наиболее полезно использовать Runnable по всем упомянутым причинам, но иногда мне нравится расширять Thread, чтобы я мог создать свой собственный метод остановки потока и вызывать его непосредственно в созданном мной потоке.

Тарварис Джексон
источник
4

Java не поддерживает множественное наследование, поэтому, если вы расширяете класс Thread, никакой другой класс расширяться не будет.

Например: если вы создаете апплет, то он должен расширять класс Applet, поэтому здесь единственный способ создать поток - реализовать интерфейс Runnable.

Химаншу Мохта
источник
4

Runnableэто интерфейс, в то время Threadкак это класс, который реализует этот интерфейс. С точки зрения дизайна, должно быть четкое разделение между тем, как задача определена, и тем, как она выполняется. Первый является обязанностью Runnalbeреализации, а второй - работа Threadкласса. В большинстве случаев реализация Runnableявляется правильным способом следовать.

developer110
источник
4

Разница между Thread и runnable. Если мы создаем Thread с использованием класса Thread, тогда номер потока равен числу созданных нами объектов. Если мы создаем поток с помощью реализуемого интерфейса, то мы можем использовать один объект для создания нескольких потоков. Таким образом, один объект совместно используется несколькими потоками. Так что это займет меньше памяти

Таким образом, в зависимости от требования, если наши данные не чувствительны. Таким образом, он может быть разделен между несколькими потоками, мы можем использовать интерфейс Runnable.

Рохит Чаг
источник
4

Добавляя мои два цента здесь - Всегда, когда это возможно, используйте implements Runnable. Ниже приведены два предостережения о том, почему вы не должны использовать extends ThreadS

  1. В идеале вы никогда не должны расширять класс Thread; Threadкласс должен быть сделан final. По крайней мере, его методы, как thread.getId(). Смотрите это обсуждение для ошибки, связанной с расширением Threads.

  2. Те, кто любит разгадывать головоломки, могут увидеть еще один побочный эффект расширения Thread. Приведенный ниже код будет печатать недоступный код, когда никто не уведомляет их.

Пожалуйста, смотрите http://pastebin.com/BjKNNs2G .

public class WaitPuzzle {

    public static void main(String[] args) throws InterruptedException {
        DoNothing doNothing = new DoNothing();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        Thread.sleep(100);
        doNothing.start();
        while(true) {
            Thread.sleep(10);
        }
    }


    static class WaitForever extends  Thread {

        private DoNothing doNothing;

        public WaitForever(DoNothing doNothing) {
            this.doNothing =  doNothing;
        }

        @Override
        public void run() {
            synchronized (doNothing) {
                try {
                    doNothing.wait(); // will wait forever here as nobody notifies here
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Unreachable Code");
            }
        }
    }

    static class DoNothing extends Thread {

        @Override
        public void run() {
            System.out.println("Do Nothing ");
        }
    } 
}
Веритас
источник