Pytanie "Implementuje Runnable" kontra "extends Thread"


Od kiedy spędziłem wątki w Javie, znalazłem te dwa sposoby pisania wątków:

Z implements Runnable:

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

Lub z extends Thread:

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

Czy istnieją znaczące różnice w tych dwóch blokach kodu?


1797
2018-02-12 14:28


pochodzenie


Dzięki za to pytanie, odpowiedzi wyjaśniły wiele nieporozumień, które miałem. Sprawdziłem poprawny sposób tworzenia wątków w Javie zanim istniało SO i istniało wiele dezinformacji / nieaktualnych informacji. - James McMahon
istnieje jeden powód, dla którego możesz chcieć przedłużyć wątek (ale nie polecam tego), możesz zapobiegawczo sobie poradzić interrupt(). Ponownie, jest to pomysł, może być przydatny we właściwym przypadku, ale nie polecam go. - bestsss
Zobacz także odpowiedź, ładnie wyjaśnioną: stackoverflow.com/q/5562720/285594 - YumYumYum
@ Bestsss, próbuję wykombinować, co możesz mieć na myśli dotyczące obsługi interrupt (). Czy próbujesz zastąpić tę metodę? - Bob Cross
tak. Jako kod, klasa Wątek A może rozszerzyć dowolną klasę, podczas gdy klasa Wątek B nie może rozszerzyć żadnej innej klasy - mani deepak


Odpowiedzi:


Tak: narzędzia Runnable jest preferowanym sposobem, IMO. Naprawdę nie specjalizujesz się w zachowaniu nici. Po prostu dajesz mu coś do uruchomienia. To znaczy kompozycja jest filozoficznie "czystszy" sposób na wyjazd.

W praktyczny warunki, to znaczy, że możesz wdrożyć Runnable i rozciągają się także od innej klasy.


1447
2018-02-12 14:32



Dokładnie, dobrze. Jakie zachowanie próbujemy zastąpić w wątku, rozszerzając go? Twierdzę, że większość ludzi nie próbuje nadpisać żadnego zachowania, ale próbuje użyć zachowania Nici. - hooknc
Jako komentarz boczny, jeśli tworzysz wątek i nie wywołujesz jego metody start (), tworzysz przeciek pamięci w Javie <5 (nie dzieje się to z Runnables): stackoverflow.com/questions/107823/... - Nacho Coloma
Jedną z niewielkich zalet Runnable jest to, że jeśli w pewnych okolicznościach nie obchodzi cię, lub nie chcesz używać wątków, a chcesz tylko wykonać kod, masz opcję, aby po prostu wywołać metodę run (). na przykład (bardzo handwavy) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run() - user949300
@ user949300 można to również zrobić za pomocą extends Thread a jeśli nie chcesz, wątku, dlaczego byś zaimplementował Runnable... - m0skit0
Parafrazując Sierrę i Batesa, kluczową korzyścią z wdrożenia Runnable jest to, że dzielisz architekturę "zadania" od "biegacza". - 8bitjunkie


tl; dr: implements Runnable jest lepszy. Jednak uwaga jest ważna

Ogólnie, polecam użycie czegoś takiego Runnable zamiast Thread ponieważ pozwala zachować pracę tylko luźno powiązaną z wyborem współbieżności. Na przykład, jeśli używasz Runnable i zdecyduj później, że to w rzeczywistości nie wymaga jego własnego Threadmożesz po prostu wywołać threadA.run ().

Zastrzeżenie: Tutaj mocno odradzam stosowanie surowych wątków. Wolę używać Kaloszach i FutureTasks (Z javadoc: "Anulowane obliczenia asynchroniczne"). Integracja limitu czasu, poprawne anulowanie i łączenie wątków nowoczesnej obsługi współbieżności są znacznie bardziej użyteczne niż stosy surowych wątków.

Kontynuacja: tam jest FutureTask konstruktor co pozwala na używanie Runnables (jeśli jest to najbardziej wygodne) i nadal korzystać z nowoczesnych narzędzi współbieżności. Aby zacytować javadoc:

Jeśli nie potrzebujesz konkretnego wyniku, rozważ użycie konstrukcji formularza:

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

Tak więc, jeśli zastąpimy ich runnable z Twoim threadA, otrzymujemy:

new FutureTask<Object>(threadA, null)

Inną opcją, która pozwala być bliżej Runnables jest ThreadPoolExecutor. Możesz użyć wykonać metoda przekazania w Runnieble, aby wykonać "dane zadanie w przyszłości".

Jeśli chcesz spróbować użyć puli wątków, powyższy fragment kodu stanie się podobny do poniższego (używając znaku Executors.newCachedThreadPool () metoda fabryczna):

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());

494
2018-02-12 14:37



To jest lepsze niż zaakceptowana odpowiedź IMHO. Jedna rzecz: fragment kodu, który masz, nie zamyka executora i widzę miliony pytań, w których ludzie się mylą, tworząc nowego Egzekutora za każdym razem, gdy chcą odrodzić zadanie. es byłoby lepiej jako pole statyczne (lub wstrzyknięte), więc jest tworzone tylko raz. - artbristol
@artbristol, dzięki! Nie zgadzam się z nowym Executorem (robimy to, co sugerujesz w naszym kodzie). Pisząc oryginalną odpowiedź, próbowałem napisać minimalny kod analagiczny do oryginalnego fragmentu. Musimy mieć nadzieję, że wielu czytelników tych odpowiedzi używa ich jako punktów skaczących. Nie próbuję pisać zamiennika dla javadoc. Skutecznie piszę o tym materiał marketingowy: jeśli podoba ci się ta metoda, powinieneś zobaczyć wszystkie inne wspaniałe rzeczy, które mamy do zaoferowania ...! - Bob Cross
Wiem, że trochę spóźniam się komentować, ale radzę sobie z tym FutureTask bezpośrednio nie jest ogólnie to, co chcesz robić. ExecutorServices stworzy odpowiednie Future dla ciebie, kiedy ty submit za Runnable/Callable do nich. Podobnie dla ScheduledExecutorServices i ScheduledFuture kiedy ty schedule za Runnable/Callable. - Powerlord
@Powerlord, moim zamiarem było uczynienie fragmentów kodu pasujących do OP w jak najściślejszym zakresie. Zgadzam się, że nowa funkcja FutureTask nie jest optymalna, ale jest jasna dla wyjaśnienia. - Bob Cross


Morał historii:

Dziedzicz tylko wtedy, gdy chcesz zastąpić pewne zachowanie.

Lub raczej powinien być odczytany jako:

Odziedzicz mniej, interfejs więcej.


226
2018-03-11 15:50



To zawsze powinno być pytanie, czy zaczniesz tworzyć współbieżny obiekt! Czy potrzebujesz funkcji felgi obiektu nici? - Liebertee
Kiedy dziedziczymy z wątku, prawie zawsze chcemy przesłonić zachowanie run() metoda. - Warren Dew
Nie można przesłonić zachowania a java.lang.Thread przez przesłonięcie run() metoda. W takim przypadku musisz zastąpić start() Metoda chyba. Zwykle po prostu ponownie korzystasz z zachowania java.lang.Thread przez wstrzyknięcie twojego bloku wykonawczego do run() metoda. - sura2k


Cóż, tak wiele dobrych odpowiedzi, chcę dodać więcej na ten temat. To pomoże zrozumieć Extending v/s Implementing Thread.
Extends bardzo ściśle wiąże dwa pliki klas i może sprawić, że kodowanie będzie trudne.

Oba podejścia wykonują tę samą pracę, ale istnieją pewne różnice.
Najczęściej występująca różnica to 

  1. Gdy rozszerzasz klasę wątków, po tym czasie nie możesz rozszerzyć żadnej innej klasy, której potrzebujesz. (Jak wiesz, Java nie pozwala na dziedziczenie więcej niż jednej klasy).
  2. Gdy zaimplementujesz Runnable, możesz zapisać przestrzeń dla swojej klasy, aby rozszerzyć dowolną inną klasę w przyszłości lub teraz.

Jednak jeden znacząca różnica między implementacją Runnable a Extending Thread jest to
  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.

Poniższy przykład pomaga lepiej zrozumieć

//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 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();
 }
}

Wyjście powyższego programu.

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

W podejściu Runnable interface tworzone jest tylko jedno wystąpienie klasy i zostało udostępnione przez różne wątki. Tak więc wartość licznika jest zwiększana dla każdego dostępu do wątku.

Podczas gdy podejście klasy wątków, musisz utworzyć oddzielną instancję dla każdego dostępu do wątku. W związku z tym dla każdej instancji klasy przydzielana jest inna pamięć, a każdy ma osobny licznik, wartość pozostaje taka sama, co oznacza, że ​​nie nastąpi żaden przyrost, ponieważ żadne odwołanie do obiektu nie jest takie samo.

Kiedy używać Runnable?
Użyj interfejsu typu Runnable, aby uzyskać dostęp do tego samego zasobu z grupy wątków. Unikaj tutaj używania klasy Thread, ponieważ tworzenie wielu obiektów zużywa więcej pamięci i staje się dużym obciążeniem wydajności.

Klasa implementująca Runnable nie jest wątkiem i tylko klasą. Aby Runnable mógł stać się Threadem, musisz stworzyć instancję Thread i podać się jako cel.

W większości przypadków interfejs Runnable powinien być używany, jeśli planujesz tylko zastąpić run() metoda i nie ma innych metod Thread. Jest to ważne, ponieważ klasy nie powinny być podklasowane, chyba że programista zamierza zmodyfikować lub ulepszyć podstawowe zachowanie klasy.

Gdy istnieje potrzeba rozszerzenia klasy nadrzędnej, wdrożenie interfejsu Runnable jest bardziej odpowiednie niż użycie klasy Thread. Ponieważ możemy rozszerzyć kolejną klasę podczas implementowania interfejsu Runnable, aby utworzyć wątek.

Mam nadzieję, że to pomoże!


185
2018-03-05 14:26



Twój kod jest oczywiście błędny. Mam na myśli, że robi to, co robi, ale nie to, co zamierzałeś pokazać. - zEro
Aby wyjaśnić: w przypadku działającego przypadku użyłeś tej samej instancji ImplementsRunnable, aby uruchomić wiele wątków, podczas gdy w przypadku Thread tworzysz różne instancje ExtendsThread, które w oczywisty sposób prowadzą do zachowania, które pokazałeś. Druga połowa Twojej głównej metody powinna być: 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();  Czy jest jaśniejszy? - zEro
Nie rozumiem jeszcze twojego zamiaru, ale moim celem było to, że jeśli tworzysz wiele instancji ExtendsThread - wszystkie one zwrócą 1 (jak pokazałeś). Możesz uzyskać takie same wyniki dla Runnable, wykonując to samo, czyli tworząc wiele instancji narzędzia ImplementsRunnable. - zEro
@zEro Cześć, jestem z przyszłości. Biorąc pod uwagę twoją wersję kodu Thread Inkrementacja również jest stwierdzeniem 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 to źle? Jeśli nie, to co jest tego dowodem? - Evil Washing Machine
@EvilWashingMachine: Byłem nieaktywny przez długi .. i właśnie zobaczyłem to. Dodałem kod Hash obiektu do instrukcji drukowania ... + " hashcode: " + this.hashCode() - zEro


Jedna rzecz, która mnie zaskoczyła, nie została jeszcze wspomniana Runnable uelastycznia twoją klasę.

Jeśli rozwiniesz wątek, to akcja, którą wykonujesz, zawsze będzie w wątku. Jeśli jednak wdrożysz Runnable to nie musi być. Możesz uruchomić go w wątku lub przekazać go do jakiejś usługi typu executor lub po prostu przekazać go jako zadanie w ramach pojedynczej aplikacji gwintowanej (może być uruchomiony w późniejszym czasie, ale w tym samym wątku). Opcje są o wiele bardziej otwarte, jeśli po prostu skorzystasz Runnable niż jeśli się przywiązujesz Thread.


73
2018-02-12 14:51



Cóż, możesz zrobić to samo z Thread obiekt też, ponieważ Thread implements Runnable... ;-) Ale "czuje się lepiej" robiąc to z pomocą Runnable niż robienie ich z Thread! - siegi
To prawda, ale Thread dodaje wiele dodatkowych rzeczy, których nie potrzebujesz, aw wielu przypadkach nie chce. Zawsze lepiej jest wdrażać interfejs pasujący do tego, co faktycznie robisz. - Herms


Jeśli chcesz zaimplementować lub rozszerzyć dowolną inną klasę Runnable Interfejs jest najkorzystniejszy, jeśli nie chcesz, aby jakakolwiek inna klasa była rozszerzona lub implementowana Thread klasa jest preferowana

Najczęściej występująca różnica to

enter image description here

Kiedy ty extends Thread klasa, po której nie możesz rozszerzyć żadnej innej klasy, której potrzebujesz. (Jak wiesz, Java nie pozwala na dziedziczenie więcej niż jednej klasy).

Kiedy ty implements Runnable, możesz zapisać miejsce, w którym twoja klasa może rozszerzyć dowolną inną klasę w przyszłości lub teraz.

  • Java nie obsługuje dziedziczenia wielokrotnego, co oznacza, że ​​można rozszerzyć tylko jedną klasę w Javie, więc po rozszerzeniu klasy Thread straciłeś szansę i nie możesz rozszerzyć ani odziedziczyć innej klasy w Javie.

  • W programowaniu zorientowanym na obiekt rozszerzenie klasy oznacza zazwyczaj dodawanie nowych funkcji, modyfikowanie lub ulepszanie zachowań. Jeśli nie robimy żadnych modyfikacji w wątku, użyj zamiast tego interfejsu Runnable.

  • Interfejs, który można uruchomić, reprezentuje zadanie, które może zostać wykonane za pomocą zwykłego wątku lub executorów lub w jakikolwiek inny sposób. logiczna separacja zadania jako działającego niż wątek jest dobrą decyzją projektową.

  • Oddzielenie zadania jako Runnable oznacza, że ​​możemy ponownie wykorzystać to zadanie, a także możemy wykonać je z różnych środków. ponieważ nie można ponownie uruchomić wątku po jego zakończeniu. ponownie Runnable vs Thread for task, Runnable jest zwycięzcą.

  • Programista Javy rozpoznaje to i dlatego Executory akceptują Runnable as Task i mają wątek roboczy, który wykonuje te zadania.

  • Dziedziczenie wszystkich metod Thread jest dodatkowym obciążeniem tylko dla reprezentowania zadania, które można łatwo wykonać przy użyciu Runnable.

Dzięki uprzejmości javarevisited.blogspot.com

Były to niektóre istotne różnice między wątkami i Runnable w Javie, jeśli znasz jakieś inne różnice w wątku vs Runnable niż proszę podzielić się nim poprzez komentarze. Osobiście używam Runnable over Thread w tym scenariuszu i zaleca użycie interfejsu Runnable lub Callable w zależności od wymagań.

Jednak znacząca różnica jest.

Kiedy ty extends Thread klasa, każdy z twoich wątków tworzy unikalny obiekt i kojarzy się z nim. Kiedy ty implements Runnable, dzieli ten sam obiekt z wieloma wątkami.


65
2018-05-11 08:59





Właściwie to nie jest mądre porównanie Runnable i Thread ze sobą.

Ta dwójka ma zależność i relację w wielowątkowości, tak jak Wheel and Engine związek pojazdu mechanicznego.

Powiedziałbym, że istnieje tylko jeden sposób wielowątkowości z dwoma krokami. Pozwolę sobie przedstawić mój punkt widzenia.

Runnable:
Podczas wdrażania interface Runnable oznacza to, że tworzysz coś, co jest run able w innym wątku. Teraz tworzenie czegoś, co można uruchomić wewnątrz wątku (działającego w wątku), nie oznacza tworzenia wątku.
A więc klasa MyRunnable to nic innego jak zwykła klasa z void run metoda. A obiektami będą zwykłe obiekty z tylko jedną metodą run który będzie wykonywany normalnie po wywołaniu. (chyba, że ​​przekazujemy obiekt w wątku).

Wątek:
class ThreadPowiedziałbym, że jest to bardzo szczególna klasa z możliwością rozpoczęcia nowego wątku, który faktycznie umożliwia wielowątkowość poprzez jego start() metoda.

Dlaczego nie mądre porównanie?
Ponieważ potrzebujemy ich obu do wielowątkowości.

W przypadku wielowątkowości potrzebujemy dwóch rzeczy:

  • Coś, co można uruchomić w wątku (Runnable).
  • Coś, co może rozpocząć nowy wątek (wątek).

Technicznie i teoretycznie oba są niezbędne do rozpoczęcia wątku, jedna wola biegać i jedno będzie spraw, by działało (Lubić Wheel and Engine pojazdu mechanicznego).

Dlatego nie można rozpocząć wątku MyRunnable musisz przekazać go do instancji Thread.

Ale możliwe jest tworzenie i uruchamianie wątku tylko za pomocą class Thread ponieważ klasa Thread przybory Runnable więc wszyscy wiemy Thread także jest Runnable wewnątrz.

Wreszcie Thread i Runnable uzupełniają się nawzajem w przypadku wielowątkowości, a nie konkurenta lub zamiennika.


61
2018-05-12 13:50



Dokładnie! To powinna być zaakceptowana odpowiedź. BTW Myślę, że pytanie zostało zmienione i ThreadA nie ma już sensu - idelvall
przyjęta odpowiedź jest znacznie bardziej delegat dzięki za odpowiedź @idelvall - Saif


Powinieneś zaimplementować Runnable, ale jeśli używasz Java 5 lub wyższej, nie powinieneś go uruchamiać new Thread ale użyj ExecutorService zamiast. Aby uzyskać szczegółowe informacje, patrz: Jak zaimplementować proste wątki w Javie.


41
2018-02-12 14:41



Nie sądzę, że ExecutorService byłby przydatny, gdybyś chciał uruchomić pojedynczy wątek. - Powerlord
Z tego, czego się nauczyłem, nie należy już samemu uruchamiać wątku w ogóle, ponieważ pozostawienie go w usłudze executora daje o wiele lepszą kontrolę (np. Czekanie na zawieszenie wątku). Ponadto nie widzę niczego w pytaniu, które sugeruje, że chodzi o jeden wątek. - Fabian Steeg
Jaki jest sens używania wielowątkowości, jeśli wiemy, że będzie to jeden wątek. Załóżmy, że mamy wiele wątków i ta odpowiedź jest cenna. - zEro
@ Zero Jestem prawie pewien, że istnieje powód, dla którego istnieje tylko jeden wątek wysyłki zdarzeń. Wątpię, żeby to był jedyny przypadek, gdyby najlepiej było mieć oddzielny wątek, ale być może nie najlepiej mieć wiele. - porcoesphino


Nie jestem ekspertem, ale mogę wymyślić jeden powód wdrożenia Runnable zamiast rozszerzenia Thread: Java obsługuje tylko dziedziczenie pojedyncze, więc możesz rozszerzyć tylko jedną klasę.

Edytuj: pierwotnie powiedział: "Wdrożenie interfejsu wymaga mniej zasobów". również, ale musisz utworzyć nową instancję wątku tak czy inaczej, więc to było złe.


31
2018-02-12 14:32



W runnerble nie możemy wykonywać połączeń sieciowych, prawda? Ponieważ mam android.os.NetworkOnMainThreadException. Ale za pomocą wątku mogę wykonywać połączenia sieciowe. Proszę, popraw mnie jeśli się mylę. - Nabeel Thobani
@NabeelThobani Normal Java nie obchodzi, ale wygląda na to, że robi to Android. Nie jestem jednak wystarczająco zaznajomiony z Androidem. - Powerlord
@NabeelThobani Oczywiście, że możesz. Prawdopodobnie nie tworzysz Nici ze swoim runnable. - m0skit0