„Implementuje Runnable” vs „wydłuża wątek” w Javie

2118

Od czasu, który spędziłem z wątkami w Javie, znalazłem 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 jest jakaś znacząca różnica w tych dwóch blokach kodu?

użytkownik65374
źródło
57
Dzięki za to pytanie, odpowiedzi wyjaśniły wiele nieporozumień, które miałem. Sprawdziłem prawidłowy sposób wykonywania wątków Java, zanim pojawiło się SO, i było tam dużo dezinformacji / nieaktualnych informacji.
James McMahon
5
istnieje jeden powód, dla którego możesz chcieć rozszerzyć Wątek ( ale nie polecam go ), możesz zapobiegawczo obsłużyć interrupt(). Znowu jest to pomysł, może być przydatny w odpowiednim przypadku, jednak nie polecam go.
bestsss
Zobacz także odpowiedź, ładnie wyjaśniona: stackoverflow.com/q/5562720/285594
@bestsss, próbuję ustalić, co masz na myśli mówiąc o obsłudze przerwań (). Czy próbujesz zastąpić metodę?
Bob Cross,
8
yes.As na kodzie, klasa nawlec może przedłużyć każdą klasę, natomiast klasa Thread B mogę przedłużyć żadnej innej klasy
mani Deepak

Odpowiedzi:

1672

Tak: narzędzia Runnableto preferowany sposób, IMO. Tak naprawdę nie specjalizujesz się w zachowaniu wątku. Po prostu dajesz temu coś do uruchomienia. Oznacza to, że kompozycja jest filozoficznie „czystszą” drogą.

W praktyce oznacza to, że możesz implementować Runnablei rozszerzać także z innej klasy.

Jon Skeet
źródło
151
Dokładnie mówiąc. Jakie zachowanie próbujemy zastąpić w wątku, rozszerzając go? Twierdziłbym, że większość ludzi nie próbuje zastąpić żadnego zachowania, ale próbuje użyć zachowania Wątku.
hooknc
87
Na marginesie, jeśli tworzysz Wątek i nie wywołujesz metody start (), tworzysz przeciek pamięci w Javie <5 (nie dzieje się tak w Runnables): stackoverflow.com/questions/107823/...
Nacho Coloma
25
Jedną niewielką zaletą Runnable jest to, że jeśli w pewnych okolicznościach nie obchodzi Cię to lub nie chcesz używać wątków, a po prostu chcesz wykonać kod, możesz po prostu wywołać run (). np. (bardzo handwavy) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
user949300
10
@ user949300 można również zrobić z extends Thread, a jeśli nie chcesz gwintowania dlaczego byś nawet wdrożyć Runnable...
m0skit0
30
Parafrazując Sierra i Batesa, główną korzyścią z wdrożenia Runnable jest to, że pod względem architektonicznym oddzielasz „zadanie” od „biegacza”.
8bitjunkie
569

tl; dr: implementuje Runnable jest lepszy. Zastrzeżenie jest jednak ważne

Ogólnie rzecz biorąc, zalecałbym Runnableraczej użycie czegoś takiego , niż Threadponieważ pozwala to na luźne powiązanie pracy z wyborem współbieżności. Na przykład, jeśli użyjesz a Runnablei zdecydujesz później, że tak naprawdę nie wymaga on własnego Thread, możesz po prostu wywołać threadA.run ().

Uwaga: W tym miejscu zdecydowanie odradzam stosowanie surowych nici. Wolę używać Callables i FutureTasks (z javadoc: „Obliczanie asynchroniczne z możliwością anulowania”). Integracja limitów czasu, prawidłowe anulowanie i pula wątków współczesnej obsługi współbieżności są dla mnie znacznie bardziej przydatne niż stosy surowych wątków.

Kontynuacja: istnieje FutureTaskkonstruktor, który pozwala korzystać z Runnables (jeśli jest to najbardziej wygodne) i nadal korzystać z nowoczesnych narzędzi do 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)

Jeśli więc zastąpimy je runnableTwoim threadA, otrzymamy:

new FutureTask<Object>(threadA, null)

Inną opcją, która pozwala pozostać bliżej Runnables, jest ThreadPoolExecutor . Możesz użyć metody execute , aby przekazać Runnable do wykonania „danego zadania w przyszłości”.

Jeśli chcesz spróbować użyć puli wątków, powyższy fragment kodu stałby się podobny do następującego (przy użyciu metody fabrycznej Executors.newCachedThreadPool () ):

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());
Bob Cross
źródło
40
To jest lepsze niż zaakceptowana odpowiedź IMHO. Jedno: fragment kodu, który masz, nie zamyka executora i widzę miliony pytań, w których ludzie mylą się, tworząc nowego executora za każdym razem, gdy chcą odrodzić zadanie. esbyłoby lepsze jako pole statyczne (lub wstrzykiwane), więc jest tworzone tylko raz.
artbristol,
7
@artbristol, dzięki! Nie zgadzam się co do nowego Executora (robimy to, co sugerujesz w naszym kodzie). Pisząc oryginalną odpowiedź, próbowałem napisać minimalny kod analogicznie do oryginalnego fragmentu. Mamy nadzieję, że wielu czytelników tych odpowiedzi wykorzysta je jako punkt wyjścia. Nie próbuję pisać zamiennika javadoc. Skutecznie piszę dla niego materiały marketingowe: jeśli podoba ci się ta metoda, powinieneś zobaczyć wszystkie inne wspaniałe rzeczy, które mamy do zaoferowania ...!
Bob Cross
3
Wiem, że trochę spóźniam się z komentowaniem tego, ale radzenie sobie z tym na FutureTaskogół nie jest tym, co chcesz robić. ExecutorServices stworzy odpowiedni Futuredla ciebie, gdy / im. Podobnie dla s i kiedy a / . submitRunnableCallableScheduledExecutorServiceScheduledFuturescheduleRunnableCallable
Władca
3
@Powerlord, moim zamiarem było stworzenie fragmentów kodu, które pasowałyby do OP tak dokładnie, jak to możliwe. Zgadzam się, że nowy FutureTask nie jest optymalny, ale jest wyjaśniony dla celów wyjaśnienia.
Bob Cross
257

Morał historii:

Dziedzicz tylko, jeśli chcesz zastąpić niektóre zachowania.

A raczej należy to rozumieć jako:

Dziedzicz mniej, interfejs więcej.

panzerschreck
źródło
1
To zawsze powinno być pytanie, jeśli zaczniesz tworzyć jednocześnie działający obiekt! Czy potrzebujesz nawet funkcji wątku?
Liebertee
4
Dziedzicząc po wątku, prawie zawsze chce się przesłonić zachowanie run()metody.
Warren Dew
1
Nie można przesłonić zachowania a java.lang.Threadpoprzez przesłonięcie run()metody. W takim przypadku musisz zastąpić start()metodę. Zwykle po prostu ponownie wykorzystujesz zachowanie java.lang.Threadpoprzez wstrzyknięcie bloku wykonawczego do run()metody.
sura2k
Dziedziczenie służy nie tylko do zastąpienia niektórych zachowań, ale także do korzystania z typowych zachowań. I odwrotnie, im więcej przesłonięć, tym gorsza hierarchia.
peja
232

Cóż, tyle dobrych odpowiedzi, chcę dodać więcej na ten temat. Pomoże to zrozumieć Extending v/s Implementing Thread.
Rozszerza bardzo ściśle wiąże dwa pliki klas i może sprawić, że niektóre z nich będą bardzo trudne w obsłudze kodu.

Oba podejścia wykonują tę samą pracę, ale były pewne różnice.
Najczęstszą różnicą jest

  1. Po rozszerzeniu klasy wątku nie można już rozszerzyć wymaganej klasy. (Jak wiadomo, Java nie pozwala na dziedziczenie więcej niż jednej klasy).
  2. Po wdrożeniu Runnable możesz zaoszczędzić miejsce dla swojej klasy, aby rozszerzyć dowolną inną klasę w przyszłości lub teraz.

Jednak jedną znaczącą różnicą między implementacją Runnable a rozszerzaniem 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 pomoże ci 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 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();
 }
}

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 opartym na interfejsie Runnable tworzona jest tylko jedna instancja klasy i została ona współużytkowana przez różne wątki. Tak więc wartość licznika jest zwiększana dla każdego dostępu do wątku.

Natomiast w podejściu do klasy wątków musisz utworzyć osobną 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żda ma osobny licznik, wartość pozostaje taka sama, co oznacza, że ​​nie nastąpi przyrost, ponieważ żadne odwołanie do obiektu nie jest takie samo.

Kiedy używać Runnable?
Użyj interfejsu Runnable, jeśli chcesz uzyskać dostęp do tych samych zasobów z grupy wątków. Unikaj tutaj używania klasy wątków, ponieważ tworzenie wielu obiektów zużywa więcej pamięci i staje się to dużym obciążeniem wydajnościowym.

Klasa implementująca Runnable nie jest wątkiem, a jedynie klasą. Aby Runnable mógł zostać Wątkiem, musisz utworzyć instancję Wątka i przekazać się jako cel.

W większości przypadków interfejs Runnable powinien być używany, jeśli planujesz zastąpić run()metodę, a nie inne metody wątku. Jest to ważne, ponieważ klasy nie powinny być podklasowane, chyba że programista zamierza zmodyfikować lub poprawić podstawowe zachowanie klasy.

Gdy zachodzi potrzeba rozszerzenia nadklasy, implementacja interfejsu Runnable jest bardziej odpowiednia niż użycie klasy Thread. Ponieważ możemy rozszerzyć kolejną klasę podczas implementacji interfejsu Runnable, aby utworzyć wątek.

Mam nadzieję, że to pomoże!

Rupesh Yadav
źródło
40
Twój kod jest ewidentnie niepoprawny. To znaczy, robi to, co robi, ale nie to, co zamierzałeś pokazać.
zEro
38
Wyjaśnienie: w przypadku uruchomionym użyłeś tej samej instancji ImplementsRunnable, aby uruchomić wiele wątków, podczas gdy w przypadku wątku tworzysz różne wystąpienia ExtendsThread, co oczywiście prowadzi do pokazanego zachowania. Druga połowa twojej głównej metody powinna brzmieć: 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 bardziej przejrzysta?
zEro
19
Nie rozumiem jeszcze twoich intencji, ale miałem na myśli to, że jeśli utworzysz wiele instancji ExtendsThread - wszystkie zwrócą 1 (jak pokazano). Możesz uzyskać te same wyniki dla Runnable, robiąc to samo, tj. Tworząc wiele instancji ImplementsRunnable.
zEro
3
@zEro Cześć, jestem z przyszłości. Biorąc pod uwagę, że twoja wersja kodu również ma Threadfunkcję inkrementacji, czy to stwierdzenie jest by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instancebłędne? Jeśli nie, to co to pokazuje?
Zła pralka,
4
Kod zamieszczony tutaj jest mylący i może powodować ból głowy. Dzięki @zEro za trochę wyjaśnienia.
Nikos,
78

Jedną z rzeczy, które mnie zaskakują, nie wspomniano jeszcze, że implementacja Runnableczyni twoją klasę bardziej elastyczną.

Jeśli rozszerzysz wątek, czynność, którą wykonujesz, zawsze będzie w wątku. Jeśli jednak wdrożysz, Runnablenie musi tak być. Możesz uruchomić go w wątku lub przekazać go do jakiejś usługi modułu wykonującego, lub po prostu przekazać jako zadanie w ramach jednej aplikacji wątkowej (może być uruchomione później, ale w tym samym wątku). Opcje są o wiele bardziej otwarte, jeśli po prostu użyjesz, Runnableniż jeśli będziesz się z nimi wiązać Thread.

Herms
źródło
6
Cóż, możesz faktycznie zrobić to samo z Threadprzedmiotem, ponieważ Thread implements Runnable… ;-) Ale „czuje się lepiej”, robiąc te rzeczy za pomocą Runnableniż za pomocą Thread!
siegi
7
To prawda, ale Threaddodaje wiele dodatkowych rzeczy, których nie potrzebujesz, aw wielu przypadkach nie chcesz. Zawsze lepiej jest wdrożyć interfejs, który pasuje do tego, co faktycznie robisz.
Herms
74

Jeśli chcesz zaimplementować lub rozszerzyć dowolną inną klasę, Runnableinterfejs jest najbardziej preferowany, w przeciwnym razie, jeśli nie chcesz, aby żadna inna klasa rozszerzała lub implementowała, Threadpreferowana jest klasa.

Najczęstszą różnicą jest

wprowadź opis zdjęcia tutaj

Podczas extends Threadzajęć nie możesz już przedłużyć żadnej innej wymaganej klasy. (Jak wiadomo, Java nie pozwala na dziedziczenie więcej niż jednej klasy).

Kiedy możesz implements Runnable, możesz zaoszczędzić miejsce na zajęciach, aby przedłużyć dowolną inną klasę w przyszłości lub teraz.

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

  • W programowaniu obiektowym rozszerzenie klasy zazwyczaj oznacza dodanie nowej funkcjonalności oraz modyfikację lub poprawę zachowań. Jeśli nie wprowadzamy żadnych modyfikacji w wątku, użyj zamiast tego interfejsu Runnable.

  • Interfejs uruchamialny reprezentuje zadanie, które można wykonać za pomocą zwykłego wątku lub executorów lub w inny sposób. więc logiczne rozdzielenie Zadania jako Runnable niż Thread jest dobrą decyzją projektową.

  • Oddzielenie zadania jako Wykonalnego oznacza, że ​​możemy ponownie użyć zadania, a także ma swobodę wykonywania go z różnych środków. ponieważ nie można ponownie uruchomić wątku po jego zakończeniu. ponownie Runnable vs Thread dla zadania, Runnable jest zwycięzcą.

  • Projektant Java rozpoznaje to i dlatego Executors akceptują Runnable jako Task i mają wątek roboczy, który wykonuje to zadanie.

  • Dziedziczenie wszystkich metod wątków jest dodatkowym obciążeniem tylko dla przedstawienia zadania, które można łatwo wykonać za pomocą Runnable.

Dzięki uprzejmości javarevisited.blogspot.com

Były to niektóre z istotnych różnic między Threadem a Runnable w Javie. Jeśli znasz jakieś inne różnice między Wątkiem a Runnable, udostępnij je za pomocą komentarzy. Ja osobiście używam Runnable zamiast Thread w tym scenariuszu i zaleca używanie interfejsu Runnable lub Callable na podstawie twoich wymagań.

Jednak istotną różnicą jest.

Podczas extends Threadzajęć każdy wątek tworzy unikalny obiekt i łączy się z nim. Kiedy Ty implements Runnabledzieli ten sam obiekt z wieloma wątkami.

Nidhish Krishnan
źródło
73

Właściwie, to nie jest mądre, aby porównać Runnablei Threadze sobą nawzajem.

Te dwa mają zależność i związek w wielowątkowości, podobnie jak Wheel and Enginezwiązek z pojazdem silnikowym.

Powiedziałbym, że jest tylko jeden sposób na wielowątkowość z dwoma krokami. Pozwól mi powiedzieć.

Runnable:
Podczas implementacji interface Runnableoznacza to, że tworzysz coś, co znajduje się run ablew innym wątku. Teraz tworzenie czegoś, co może działać w wątku (uruchamialne w wątku), nie oznacza tworzenia wątku.
Klasa MyRunnablejest więc zwykłą klasą z void runmetodą. I to będą obiekty zwykłe z tylko metodą, runktóra zadziała normalnie po wywołaniu. (chyba że przekażemy obiekt w wątku).

Wątek:
class Thread powiedziałbym, że jest to bardzo specjalna klasa z możliwością uruchomienia nowego wątku, który faktycznie umożliwia wielowątkowość za pomocą tej start()metody.

Dlaczego nie warto porównywać?
Ponieważ potrzebujemy ich obu do wielowątkowości.

Do wielowątkowości potrzebujemy dwóch rzeczy:

  • Coś, co może działać w wątku (Runnable).
  • Coś, co może rozpocząć nowy wątek (wątek).

Tak więc technicznie i teoretycznie oba z nich są konieczne, aby rozpocząć wątek, jeden będzie działać, a drugi sprawi, że zacznie działać (jak Wheel and Enginew przypadku pojazdu silnikowego).

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

Ale możliwe jest, aby utworzyć i uruchomić wątku tylko przy użyciu class Threadponieważ klasa Threadimplementuje Runnabletak wszyscy wiemy, Threadtakże jest Runnablew środku.

Wreszcie Threadi Runnableuzupełniają się nawzajem w przypadku wielowątkowości, a nie konkurencji lub wymiany.

Saif
źródło
3
Dokładnie! To powinna być zaakceptowana odpowiedź. BTW Myślę, że pytanie zostało zredagowane i ThreadAnie ma już sensu
idelvall,
zaakceptowana odpowiedź jest znacznie bardziej wdzięczna za odpowiedź @idelvall
Saif
Najlepsza odpowiedź! Dzięki!
Michael:
44

Powinieneś zaimplementować Runnable, ale jeśli pracujesz na Javie 5 lub wyższej, nie powinieneś go uruchamiać, new Threadale zamiast tego użyj ExecutorService . Aby uzyskać szczegółowe informacje, zobacz: Jak wdrożyć proste wątkowanie w Javie .

Fabian Steeg
źródło
5
Nie sądzę, że ExecutorService byłby tak przydatny, gdybyś chciał uruchomić pojedynczy wątek.
Władca
1
Z tego, czego się nauczyłem, nie należy już samodzielnie uruchamiać wątku, ponieważ pozostawienie go w usłudze executora sprawia, że ​​wszystko jest o wiele bardziej kontrolowane (na przykład oczekiwanie na zawieszenie wątku). Ponadto nie widzę nic w pytaniu, które sugerowałoby, że dotyczy jednego wątku.
Fabian Steeg
4
Po co używać wielowątkowości, jeśli wiemy, że będzie to pojedynczy wątek. Załóżmy więc, że mamy wiele wątków i ta odpowiedź jest cenna.
zEro
1
@zEro Jestem pewien, że istnieje powód, dla którego istnieje tylko jeden wątek wysyłki zdarzeń. Wątpię, żeby to był jedyny przypadek, w którym najlepiej mieć osobny wątek, ale być może nie najlepiej mieć wiele.
porcoesphino,
33

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

Edycja: pierwotnie było powiedziane: „Implementacja interfejsu wymaga mniej zasobów”. ale musisz utworzyć nową instancję wątku tak czy inaczej, więc było to źle.

Władca
źródło
Czy można uruchomić, nie możemy wykonywać połączeń sieciowych? Jak 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 ma znaczenia, ale wygląda na to, że Android tak. Jednak nie znam wystarczająco Androida.
Powerlord,
1
@NabeelThobani Oczywiście, że możesz. Prawdopodobnie nie tworzysz Wątku za pomocą Runnable.
m0skit0
20

Powiedziałbym, że istnieje trzeci sposób:

public class Something {

    public void justAnotherMethod() { ... }

}

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

Być może trochę na to wpływa moje ostatnie użycie JavaScript i ActionScript 3, ale w ten sposób twoja klasa nie musi implementować dość niejasnego interfejsu Runnable.

Bart van Heukelom
źródło
38
To nie jest tak naprawdę trzeci sposób. Nadal wdrażasz Runnable, robiąc to anonimowo.
Don Roby
2
@Don Roby: Który jest inny. Jest to często wygodne i można użyć pól i końcowych zmiennych lokalnych z zawierającej klasy / metody.
Bart van Heukelom,
6
@BartvanHeukelom Jest to wygodne, ale nie inne. Możesz to zrobić z dowolnym rodzajem klasy zagnieżdżonej, tj. Klasami wewnętrznymi, klasami lokalnymi i wyrażeniami lambda.
xehpuk
18

Wraz z wydaniem Java 8 istnieje teraz trzecia opcja.

Runnablejest interfejsem funkcjonalnym , co oznacza, że ​​jego instancje można tworzyć za pomocą wyrażeń lambda lub odwołań do metod.

Twój przykład można zastąpić:

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

lub jeśli chcesz użyć ExecutorServiceodwołania do metody i:

executor.execute(runner::run)

To nie są tylko znacznie krótszy niż swoich przykładach, ale również pochodzić z wielu zalet wskazanych w innych odpowiedzi korzystania Runnablenad Thread, jak i za pomocą pojedynczego odpowiedzialności kompozycję, ponieważ nie jesteś specjalizujący zachowanie wątku. W ten sposób unika się również tworzenia dodatkowej klasy, jeśli wszystko czego potrzebujesz to tak Runnablejak w przykładach.

Alex
źródło
Ta odpowiedź wymaga wyjaśnienia. Po kilku zagadkach doszedłem do wniosku, że () -> {}ma reprezentować niestandardową logikę, której ktoś potrzebuje? Więc lepiej byłoby powiedzieć jako () -> { /* Code here */ }?
ToolmakerSteve
17

Tworzenie instancji interfejsu zapewnia czystszą separację między kodem a implementacją wątków, dlatego w tym przypadku wolałbym implementować Runnable.

starblue
źródło
12

Wydaje się, że wszyscy tutaj myślą, że wdrożenie Runnable jest właściwą drogą i tak naprawdę nie zgadzam się z nimi, ale moim zdaniem istnieje również możliwość rozszerzenia Thread, w rzeczywistości wykazaliście to w swoim kodzie.

Jeśli implementujesz Runnable, klasa, która implementuje Runnable, nie ma kontroli nad nazwą wątku, to kod wywołujący może ustawić nazwę wątku, tak jak:

new Thread(myRunnable,"WhateverNameiFeelLike");

ale jeśli rozszerzysz wątek, możesz zarządzać tym w samej klasie (tak jak w twoim przykładzie nazywasz wątek „ThreadB”). W takim przypadku:

A) może nadać mu bardziej przydatną nazwę do celów debugowania

B) wymuszają używanie tej nazwy we wszystkich instancjach tej klasy (chyba że zignorujesz fakt, że jest to wątek i zrobisz to z nim tak, jakby to był Runnable, ale mówimy tutaj o konwencji, więc w każdym razie zignoruj ​​tę możliwość, którą czuję).

Możesz nawet na przykład pobrać ślad stosu jego utworzenia i użyć go jako nazwy wątku. Może się to wydawać dziwne, ale w zależności od struktury kodu może być bardzo przydatne do celów debugowania.

Może się to wydawać małą rzeczą, ale w przypadku bardzo złożonej aplikacji z dużą liczbą wątków i nagle wszystko „zatrzymało się” (albo z powodu impasu, albo być może z powodu wady protokołu sieciowego, który byłby mniejszy oczywiste - lub z innych niekończących się powodów), a następnie zrzut zrzutu stosu z Javy, gdzie wszystkie wątki są nazywane „Thread-1”, „Thread-2”, „Thread-3” nie zawsze jest bardzo użyteczny (zależy to od tego, jakie są twoje wątki ustrukturyzowane i czy można pożytecznie stwierdzić, który jest po prostu przez ich ślad stosu - nie zawsze jest to możliwe, jeśli używasz grup wielu wątków, wszystkie z tym samym kodem).

Powiedziawszy, że możesz oczywiście zrobić powyższe w sposób ogólny, tworząc rozszerzenie klasy wątku, która ustawia swoją nazwę na ślad stosu wywołania jej utworzenia, a następnie użyj tego z implementacjami Runnable zamiast standardowej klasy wątku java (patrz poniżej), ale oprócz śledzenia stosu może być więcej informacji specyficznych dla kontekstu, które byłyby przydatne w nazwie wątku do debugowania (odwołanie do jednej z wielu kolejek lub gniazd, które mógłby przetwarzać, na przykład, w którym to przypadku wolisz rozszerz Wątek specjalnie dla tego przypadku, aby kompilator zmusił Ciebie (lub inne osoby korzystające z twoich bibliotek) do przekazania pewnych informacji (np. kolejki / gniazda) do użycia w nazwie).

Oto przykład ogólnego wątku ze śladami stosu wywołującego jako jego nazwą:

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"));
    }
}

a oto próbka wyników porównująca dwie nazwy:

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]
Antonim
źródło
cHao, o co ci chodzi? Nie można użyć powyższego kodu podczas wykonywania wątku, aby uzyskać ślad stosu tworzenia wątków (zamiast tego można uzyskać prostą nazwę lub co najwyżej ślad stosu uruchamiania wątków), ale poprzez podklasowanie wątku można to dokładnie zrobić i wymusić wymaga nawet dalszych informacji specyficznych dla kontekstu, co daje dokładniejsze zrozumienie, który wątek może mieć problem.
AntonyM,
8
Chodzi mi o to, że „Jeśli zaimplementujesz Runnable, to klasa, która implementuje Runnable, nie ma kontroli nad nazwą wątku ...” jest wyraźnie fałszywa. Implementacja klasy Runnablemoże rzeczywiście kontrolować nazwę wątku, ponieważ wątek uruchamiający kod jest z definicji bieżącym wątkiem (a każdy kod, który przechodzi testy bezpieczeństwa, ma kontrolę nad nazwami wątków). Biorąc pod uwagę, że poświęcisz połowę swojego postu na „omg, a co z nazwami wątków!”, Wydaje się to dość poważna sprawa.
cHao
Nazwa wątku? Nic nie stoi na przeszkodzie, abyś rozszerzył klasę wątków.
RichieHH,
11

Można uruchomić, ponieważ:

  • Pozostawia większą elastyczność implementacji Runnable w celu rozszerzenia o inną klasę
  • Oddziela kod od wykonania
  • Pozwala na uruchomienie twojego runnable z puli wątków, wątku zdarzenia lub w jakikolwiek inny sposób w przyszłości.

Nawet jeśli nie potrzebujesz tego teraz, możesz w przyszłości. Ponieważ zastąpienie wątku nie przynosi korzyści, Runnable jest lepszym rozwiązaniem.

n13
źródło
11

Ponieważ jest to bardzo popularny temat, a dobre odpowiedzi są szeroko rozpowszechnione i omawiane z wielką głębią, uznałem, że uzasadnione jest skompilowanie dobrych odpowiedzi od innych w bardziej zwięzłej formie, dzięki czemu nowi użytkownicy mają z góry łatwy przegląd:

  1. Zazwyczaj rozszerzasz klasę, aby dodać lub zmodyfikować funkcjonalność. Tak więc, jeśli nie chcesz , aby zastąpić dowolny zachowanie wątku , a następnie użyć Runnable.

  2. W tym samym świetle, jeśli nie trzeba do dziedziczą metod wątku, można to zrobić bez tego narzutu przy użyciu Runnable.

  3. Pojedyncze dziedziczenie : Jeśli rozszerzysz wątek, nie możesz rozszerzyć go z żadnej innej klasy, więc jeśli to jest to, co musisz zrobić, musisz użyć Runnable.

  4. Jest to dobry projekt odrębnej logiki domeny z środków technicznych, w tym sensie, że lepiej jest mieć Runnable zadanie izolować swoje zadanie ze swojej biegacza .

  5. Możesz wykonać ten sam obiekt Runnable wiele razy , jednak obiekt Thread można uruchomić tylko raz. (Być może powód, dla którego Wykonawcy akceptują Runnable, ale nie wątki.)

  6. Jeśli rozwiniesz swoje zadanie jako Runnable, masz pełną elastyczność, jak korzystać z niego teraz iw przyszłości . Możesz uruchomić go jednocześnie przez Executory, ale także przez Thread. I nadal możesz używać / wywoływać go nie jednocześnie w tym samym wątku, tak jak każdy inny zwykły typ / obiekt.

  7. To sprawia, że łatwiejsze do odrębnych zadań-logicznych i współbieżności aspektów w swoich testów jednostkowych .

  8. Jeśli jesteś zainteresowany tym pytaniem, możesz być również zainteresowany różnicą między wywoływalnymi a uruchamialnymi .

Jörg
źródło
@Pino Tak, sam wątek jest również uruchamialny. Jeśli jednak rozszerzysz go tak, aby używał go jako Runnable, jaki jest sens? Dlaczego nie użyć zwykłego Runnable bez całego bagażu. Argumentowałbym więc, że jeśli rozszerzysz Wątek, wykonasz go również przy użyciu metody start, z której można skorzystać tylko raz. Taki właśnie chciał Nidhish-Krishnan w swojej odpowiedzi. Zauważ, że moja to tylko kompilacja lub krótkie podsumowanie innych odpowiedzi tutaj.
Jörg
11

Różnica między rozszerzaniem wątku a implementacją Runnable to:

wprowadź opis zdjęcia tutaj

Raman Gupta
źródło
8

Jest to omówione w samouczku Definiowanie i rozpoczynanie wątku Oracle :

Którego z tych idiomów powinieneś użyć? Pierwszy idiom, który wykorzystuje obiekt Runnable, jest bardziej ogólny, ponieważ obiekt Runnable może podklasować klasę inną niż Thread. Drugi idiom jest łatwiejszy w użyciu w prostych aplikacjach, ale jest ograniczony faktem, że klasa zadań musi być potomkiem wątku. Ta lekcja koncentruje się na pierwszym podejściu, które oddziela zadanie Runnable od obiektu Thread, który wykonuje zadanie. To podejście jest nie tylko bardziej elastyczne, ale można je także stosować do interfejsów API zarządzania wątkami wysokiego poziomu, które zostaną omówione później.

Innymi słowy, implementacja Runnablebędzie działać w scenariuszach, w których klasa rozszerza klasę inną niż Thread. Java nie obsługuje wielokrotnego dziedziczenia. Rozszerzanie Threadnie będzie również możliwe w przypadku korzystania z niektórych interfejsów API zarządzania wątkami wysokiego poziomu. Jedynym scenariuszem, w którym Threadpreferowane jest rozszerzenie, jest mała aplikacja, która nie będzie podlegać aktualizacjom w przyszłości. Prawie zawsze lepiej jest go wdrożyć, Runnableponieważ jest on bardziej elastyczny w miarę rozwoju projektu. Zmiana projektu nie będzie miała większego wpływu, ponieważ można zaimplementować wiele interfejsów w Javie, ale rozszerzyć tylko jedną klasę.

Sionnach733
źródło
8

Najprostszym wyjaśnieniem byłoby zaimplementowanie, Runnableże możemy przypisać ten sam obiekt do wielu wątków i każdy Threadma te same stany i zachowanie tego samego obiektu.

Załóżmy na przykład, że istnieją dwa wątki, wątek 1 umieszcza liczbę całkowitą w tablicy, a wątek 2 pobiera liczby całkowite z tablicy, gdy tablica jest zapełniona. Zauważ, że aby wątek działał, musi znać stan tablicy, niezależnie od tego, czy wątek ją zapełnił, czy nie.

Implementacja Runnablepozwala na elastyczność w udostępnianiu obiektu, a jednocześnie extends Threadumożliwia tworzenie nowych obiektów dla każdego wątku, dlatego każda aktualizacja wykonywana przez wątek 1 zostaje utracona w wątku 2.

Shababb Karim
źródło
7

Jeśli się nie mylę, jest to mniej więcej podobne

Jaka jest różnica między interfejsem a klasą abstrakcyjną?

przedłuża ustanawia relację „ Jest A ”, a interfejs zapewnia możliwość „ Ma ”.

Wolę implementuje Runnable :

  1. Jeśli nie musisz rozszerzać klasy Thread i modyfikować domyślnej implementacji API Thread
  2. Jeśli wykonujesz polecenie pożaru i zapomnij
  3. Jeśli już rozszerzasz inną klasę

Wolę „ przedłuża wątek ”:

  1. Jeśli musisz przesłonić dowolną z tych metod wątków wymienionych na stronie dokumentacji Oracle

Zasadniczo nie trzeba zastępować zachowania wątku. Więc implementuje Runnable jest preferowany przez większość czasu.

Z drugiej strony korzystanie z zaawansowanych ExecutorServicelub ThreadPoolExecutorServiceAPI zapewnia większą elastyczność i kontrolę.

Spójrz na to pytanie SE:

ExecutorService vs Casual Thread Spawner

Ravindra babu
źródło
5

Oddzielenie klasy Thread od implementacji Runnable pozwala również uniknąć potencjalnych problemów z synchronizacją między wątkiem a metodą run (). Oddzielny Runnable ogólnie daje większą elastyczność w sposobie odwoływania się i wykonywania kodu wykonalnego.

Govula Srinivas
źródło
5

To S od SOLID : Single odpowiedzialność.

Gwint ucieleśnia działa kontekst (jak w kontekście wykonania: stos oraz identyfikator wątku, etc.) do asynchronicznego wykonania kawałka kodu. Idealnie ten fragment kodu powinien być tą samą implementacją, zarówno synchroniczną, jak i asynchroniczną .

Jeśli połączysz je w jedną implementację, wynikowy obiekt daje dwie niezwiązane przyczyny zmian:

  1. obsługa wątków w aplikacji (np. zapytania i modyfikacja kontekstu wykonania)
  2. algorytm zaimplementowany przez fragment kodu (część uruchamialna)

Jeśli używany język obsługuje klasy częściowe lub wielokrotne dziedziczenie, możesz posegregować każdą przyczynę w jej własnej superklasie, ale sprowadza się to do złożenia dwóch obiektów, ponieważ ich zestawy funkcji nie pokrywają się. To dotyczy teorii.

W praktyce, ogólnie rzecz biorąc, program nie musi być bardziej złożony niż to konieczne. Jeśli masz jeden wątek pracujący nad konkretnym zadaniem, bez zmiany tego zadania, prawdopodobnie nie ma sensu tworzyć oddzielnych klas zadań, a kod jest prostszy.

W kontekście Javy , ponieważ narzędzie już tam jest, prawdopodobnie łatwiej jest rozpocząć bezpośrednio od samodzielnych Runnableklas i przekazać ich instancje do Thread(lub Executor) instancji. Raz przyzwyczajony do tego wzorca, nie jest trudniejszy w użyciu (a nawet czytaniu) niż prosty przypadek wątku.

didierc
źródło
5

Jednym z powodów, dla których chcesz wdrożyć interfejs zamiast rozszerzać klasę podstawową jest to, że już rozszerzasz inną klasę. Możesz rozszerzyć tylko jedną klasę, ale możesz zaimplementować dowolną liczbę interfejsów.

Jeśli rozszerzysz Wątek, zasadniczo uniemożliwiasz wykonanie swojej logiki przez inny wątek niż „ten”. Jeśli chcesz, aby jakiś wątek wykonywał twoją logikę, lepiej po prostu zaimplementować Runnable.

Nikhil AA
źródło
Tak, implementując interfejs Runnable, można swobodnie implementować własną logikę poprzez rozszerzenie dowolnej klasy, dlatego Runnable jest bardziej preferowana niż klasa Thread.
Akash5288,
5

jeśli użyjesz Runnable, możesz zaoszczędzić miejsce, aby rozszerzyć się na dowolną inną klasę.

użytkownik2771655
źródło
5

Czy możemy ponownie odwiedzić podstawowy powód, dla którego chcieliśmy, aby nasza klasa zachowywała się jak Thread? Nie ma żadnego powodu, chcieliśmy po prostu wykonać zadanie, najprawdopodobniej w trybie asynchronicznym, co dokładnie oznacza, że ​​wykonanie zadania musi rozgałęzić się z naszego głównego wątku i głównego wątku, jeśli zakończy się wcześnie, może, ale nie musi, czekać dla rozgałęzionej ścieżki (zadanie).

Jeśli to jest cały cel, to gdzie widzę potrzebę specjalistycznego wątku. Można tego dokonać, pobierając wątek RAW z puli wątków systemu i przypisując mu nasze zadanie (może być instancją naszej klasy) i to wszystko.

Przestrzegajmy więc koncepcji OOP i napisz klasę, której potrzebujemy. Istnieje wiele sposobów robienia rzeczy, ważne jest robienie tego we właściwy sposób.

Potrzebujemy zadania, więc napisz definicję zadania, którą można uruchomić w wątku. Więc użyj Runnable.

Zawsze pamiętaj, implementsjest specjalnie używany do nadawania zachowania i extendssłuży do nadawania cechy / właściwości.

Nie chcemy własności wątku, zamiast tego chcemy, aby nasza klasa zachowywała się jako zadanie, które można uruchomić.

dharam
źródło
4

Tak, jeśli wywołasz wywołanie ThreadA, nie musisz wywoływać metody start, a metoda run jest wywołaniem po wywołaniu tylko klasy ThreadA. Ale jeśli używasz wywołania ThreadB, musisz koniecznie uruchomić wątek początkowy dla metody uruchamiania wywołania. Jeśli masz dodatkową pomoc, odpowiedz mi.

Manoj Kumar
źródło
4

Uważam, że najbardziej użyteczne jest użycie Runnable z wszystkich wymienionych powodów, ale czasami lubię rozszerzać Thread, aby móc stworzyć własną metodę zatrzymywania wątków i wywołać ją bezpośrednio w utworzonym wątku.

Tarvaris Jackson
źródło
4

Java nie obsługuje wielokrotnego dziedziczenia, więc jeśli rozszerzysz klasę Thread, żadna inna klasa nie zostanie przedłużona.

Na przykład: jeśli tworzysz aplet, musi on rozszerzyć klasę apletu, więc tutaj jedynym sposobem na utworzenie wątku jest implementacja interfejsu Runnable

Himanshu Mohta
źródło
4

Runnableto interfejs, natomiast Threadklasa, która implementuje ten interfejs. Z punktu widzenia projektowania powinna istnieć wyraźna separacja między sposobem definiowania zadania a sposobem jego wykonywania. Pierwszy z nich jest odpowiedzialny za Runnalbewdrożenie, a drugi to zadanie Threadklasy. W większości przypadków wdrożenie Runnablejest właściwym sposobem postępowania.

developer110
źródło
4

Różnica między Threadem a Runnable. Jeśli tworzymy Thread za pomocą klasy Thread, to liczba wątków jest równa liczbie utworzonych przez nas obiektów. Jeśli tworzymy wątek za pomocą implementowalnego interfejsu, możemy użyć jednego obiektu do utworzenia wielu wątków, więc jeden obiekt jest współdzielony przez wiele wątków, więc zajmie mniej pamięci

Zatem w zależności od wymagań, jeśli nasze dane nie są wrażliwe. Więc może być dzielony między wiele wątków, których możemy użyć interfejsu Runnable.

Rohit Chugh
źródło
4

Dodając tutaj moje dwa centy - Zawsze, gdy to możliwe, użyj implements Runnable. Poniżej znajdują się dwa zastrzeżenia, dlaczego nie należy używać extends Threads

  1. Idealnie nie powinieneś nigdy rozszerzać klasy Thread; Threadklasa powinna być wykonana final. Przynajmniej takie metody jak thread.getId(). Zobacz dyskusję, aby znaleźć błąd związany z rozszerzaniem Threads.

  2. Ci, którzy lubią rozwiązywać łamigłówki, mogą zobaczyć kolejny efekt uboczny rozszerzenia wątku. Poniższy kod wyświetli kod nieosiągalny, gdy nikt ich nie powiadomi.

Proszę zobaczyć 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 ");
        }
    } 
}
veritas
źródło