"реализует Runnable" против "расширяет поток" в Java - PullRequest
1941 голосов
/ 12 февраля 2009

Сколько времени я потратил на потоки в 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

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

Ответы [ 42 ]

16 голосов
/ 29 июля 2014

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

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

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

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

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

executor.execute(runner::run)

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

15 голосов
/ 12 февраля 2009

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

11 голосов
/ 20 июня 2012

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

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

new Thread(myRunnable,"WhateverNameiFeelLike");

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

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

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]
10 голосов
/ 13 февраля 2014

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

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

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

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

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

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

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

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

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

10 голосов
/ 07 мая 2010

Работоспособен потому что:

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

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

8 голосов
/ 21 января 2014

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

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

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

8 голосов
/ 01 октября 2017

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

enter image description here

6 голосов
/ 13 февраля 2016

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

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

extends устанавливает отношение " Is A " и интерфейс обеспечивает " Имеет " возможность.

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

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

Предпочитать " extends Thread ":

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

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

В другой заметке использование расширенного API ExecutorService или ThreadPoolExecutorService обеспечивает большую гибкость и контроль.

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

ExecutorService vs Casual Spawner

6 голосов
/ 14 августа 2015

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

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

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

5 голосов
/ 26 сентября 2013

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

...