"реализует 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 ]

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

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

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

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

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

В общем, я бы рекомендовал использовать что-то вроде Runnable, а не Thread, потому что это позволяет вам поддерживать работу только в слабой зависимости от вашего выбора параллелизма. Например, если вы используете 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());
245 голосов
/ 11 марта 2010

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

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

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

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

205 голосов
/ 05 марта 2013

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

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

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

Однако, одно существенное отличие между реализацией Runnable и расширяющим потоком состоит в том, что
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 стал потоком, вам нужно создать экземпляр Thread и передать себя в качестве цели.

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

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

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

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

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

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

70 голосов
/ 11 мая 2013

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

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

enter image description here

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

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

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

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

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

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

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

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

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

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

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

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

65 голосов
/ 12 мая 2015

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

19 голосов
/ 26 октября 2010

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

public class Something {

    public void justAnotherMethod() { ... }

}

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

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

...