"Implementiert Runnable" vs "erweitert Thread" in Java


2118

Seit meiner Zeit mit Threads in Java habe ich zwei Möglichkeiten gefunden, Threads zu schreiben:

Mit implements Runnable:

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

Oder mit extends Thread:

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

Gibt es einen signifikanten Unterschied zwischen diesen beiden Codeblöcken?


57
Vielen Dank für diese Frage, die Antworten haben viele Missverständnisse ausgeräumt, die ich hatte. Ich habe nach der richtigen Methode gesucht, um Java-Threads zu erstellen, bevor SO existiert, und es gab viele Fehlinformationen / veraltete Informationen.
James McMahon

5
Es gibt einen Grund, warum Sie Thread erweitern möchten ( aber ich empfehle es nicht ), den Sie präventiv behandeln können interrupt(). Auch hier ist es eine Idee, es könnte im richtigen Fall nützlich sein, aber ich empfehle es nicht.
Bests

Bitte sehen Sie auch die Antwort, schön erklärt: stackoverflow.com/q/5562720/285594

@bestsss, ich versuche herauszufinden, was Sie mit Interrupt () meinen könnten. Versuchen Sie, die Methode zu überschreiben?
Bob Cross

8
Ja. Gemäß dem Code kann Klasse Thread A jede Klasse erweitern, während Klasse Thread B keine andere Klasse erweitern kann
Mani Deepak

Antworten:


1672

Ja, Geräte Runnablesind der bevorzugte Weg, IMO. Sie sind nicht wirklich auf das Verhalten des Threads spezialisiert. Du gibst ihm nur etwas zum Laufen. Das heißt, Komposition ist der philosophisch "reinere" Weg.

In der Praxis bedeutet dies, dass Sie auch eine Runnableandere Klasse implementieren und erweitern können .


151
Genau, gut ausgedrückt. Welches Verhalten versuchen wir in Thread zu überschreiben, indem wir es erweitern? Ich würde argumentieren, dass die meisten Leute nicht versuchen, irgendein Verhalten zu überschreiben, sondern das Verhalten von Thread zu verwenden.
Hooknc

87
Wenn Sie einen Thread instanziieren und seine start () -Methode nicht aufrufen, erstellen Sie als Nebenkommentar einen Speicherverlust in Java <5 (dies ist bei Runnables nicht der Fall): stackoverflow.com/questions/107823/…
Nacho Coloma

25
Ein kleiner Vorteil von Runnable ist, dass Sie, wenn Sie sich unter bestimmten Umständen nicht darum kümmern oder kein Threading verwenden möchten und nur den Code ausführen möchten, einfach die Option run () aufrufen können. zB (sehr handgewellt) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
user949300

10
@ user949300 Sie können das auch mit tun extends Threadund wenn Sie kein Threading möchten, warum würden Sie überhaupt implementieren Runnable...
m0skit0

30
Um Sierra und Bates zu paraphrasieren: Ein wesentlicher Vorteil der Implementierung von Runnable besteht darin, dass Sie den "Job" architektonisch vom "Runner" trennen.
8bitjunkie

569

tl; dr: implementiert Runnable ist besser. Die Einschränkung ist jedoch wichtig

Im Allgemeinen würde ich empfehlen, so etwas wie zu verwenden, Runnableanstatt Threadweil es Ihnen ermöglicht, Ihre Arbeit nur lose mit Ihrer Wahl der Parallelität zu verbinden. Wenn Sie beispielsweise a verwenden Runnableund später entscheiden, dass dies tatsächlich kein eigenes erfordert Thread, können Sie einfach threadA.run () aufrufen.

Vorsichtsmaßnahme: Ich rate hier dringend von der Verwendung von Rohfäden ab. Ich bevorzuge die Verwendung von Callables und FutureTasks (aus dem Javadoc: "Eine stornierbare asynchrone Berechnung"). Die Integration von Zeitüberschreitungen, das ordnungsgemäße Abbrechen und das Thread-Pooling der modernen Parallelitätsunterstützung sind für mich viel nützlicher als Stapel von rohen Threads.

Follow-up: Es gibt einen FutureTaskKonstruktor , mit dem Sie Runnables verwenden können (wenn Sie damit am besten vertraut sind) und dennoch die Vorteile der modernen Parallelitätstools nutzen können. Um den Javadoc zu zitieren :

Wenn Sie kein bestimmtes Ergebnis benötigen, sollten Sie Konstruktionen des Formulars verwenden:

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

Also, wenn wir ihre ersetzen runnablemit Ihrem threadA, erhalten wir folgendes:

new FutureTask<Object>(threadA, null)

Eine weitere Option, mit der Sie näher an Runnables bleiben können, ist ein ThreadPoolExecutor . Sie können die Verwendung ausführen Methode in einem Runnable passieren auszuführen „um die gegebene Aufgabe irgendwann in der Zukunft.“

Wenn Sie versuchen möchten, einen Thread-Pool zu verwenden, sieht das obige Codefragment folgendermaßen aus (mithilfe der Factory-Methode Executors.newCachedThreadPool () ):

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

40
Dies ist meiner Meinung nach besser als die akzeptierte Antwort. Eine Sache: Der Codeausschnitt, den Sie haben, schließt den Executor nicht und ich sehe Millionen von Fragen, bei denen Leute dies falsch verstehen und jedes Mal einen neuen Executor erstellen, wenn sie eine Aufgabe erzeugen möchten. eswäre besser als statisches (oder injiziertes) Feld, so dass es nur einmal erstellt wird.
Artbristol

7
@ Artbristol, danke! Ich bin mit dem neuen Executor nicht einverstanden (wir tun, was Sie in unserem Code vorschlagen). Beim Schreiben der ursprünglichen Antwort habe ich versucht, minimalen Code analog zum ursprünglichen Fragment zu schreiben. Wir müssen hoffen, dass viele Leser dieser Antworten sie als Ausgangspunkt verwenden. Ich versuche nicht, einen Ersatz für den Javadoc zu schreiben. Ich schreibe effektiv Marketingmaterial dafür: Wenn Ihnen diese Methode gefällt, sollten Sie all die anderen großartigen Dinge sehen, die wir zu bieten haben ...!
Bob Cross

3
Ich weiß, dass ich etwas spät dran bin, aber FutureTaskdirekt damit umzugehen ist im Allgemeinen nicht das, was Sie tun möchten. ExecutorServices erstellt das Futurefür Sie passende , wenn Sie submitein Runnable/ Callablezu ihnen. Ebenso für ScheduledExecutorServices und ScheduledFuturewenn Sie scheduleein Runnable/ Callable.
Powerlord

3
@Powerlord, meine Absicht war es, Codefragmente zu erstellen, die so genau wie möglich mit den OPs übereinstimmen. Ich bin damit einverstanden, dass die neue FutureTask nicht optimal ist, aber zum Zwecke der Erklärung klar ist.
Bob Cross

257

Moral der Geschichte:

Erben Sie nur, wenn Sie ein bestimmtes Verhalten überschreiben möchten.

Oder besser gesagt sollte es gelesen werden als:

Weniger erben, mehr Schnittstelle.


1
Dies sollte immer die Frage sein, wenn Sie ein gleichzeitig laufendes Objekt erstellen! Benötigen Sie überhaupt die Thread Object-Funktionen?
Liebertee

4
Wenn man von Thread erbt, möchte man fast immer das Verhalten der run()Methode überschreiben .
Warren Dew

1
Sie können das Verhalten von a nicht überschreiben, java.lang.Threadindem Sie die run()Methode überschreiben . In diesem Fall müssen Sie die start()Methode überschreiben , denke ich. Normalerweise verwenden Sie das Verhalten von einfach wieder, java.lang.Threadindem Sie Ihren Ausführungsblock in die run()Methode einfügen.
sura2k

Die Vererbung dient nicht nur dazu, ein bestimmtes Verhalten zu überschreiben, sondern auch dazu, allgemeine Verhaltensweisen zu verwenden. Und es ist das Gegenteil, je mehr Überschreibungen, desto schlechter die Hierarchie.
Peja

232

Nun, so viele gute Antworten, ich möchte mehr dazu hinzufügen. Dies wird zum Verständnis beitragen Extending v/s Implementing Thread.
Extends bindet zwei Klassendateien sehr eng und kann dazu führen, dass Code nur schwer zu verarbeiten ist.

Beide Ansätze machen den gleichen Job, aber es gab einige Unterschiede.
Der häufigste Unterschied ist

  1. Wenn Sie die Thread-Klasse erweitern, können Sie danach keine andere Klasse erweitern, die Sie benötigen. (Wie Sie wissen, erlaubt Java nicht, mehr als eine Klasse zu erben.)
  2. Wenn Sie Runnable implementieren, können Sie Platz für Ihre Klasse sparen, um jede andere Klasse in Zukunft oder jetzt zu erweitern.

Ein wesentlicher Unterschied zwischen der Implementierung von Runnable und der Erweiterung von Thread besteht jedoch darin, dass
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.

Das folgende Beispiel hilft Ihnen, es besser zu verstehen

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

Ausgabe des obigen Programms.

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

Beim Runnable-Schnittstellenansatz wird nur eine Instanz einer Klasse erstellt und von verschiedenen Threads gemeinsam genutzt. Der Wert des Zählers wird also für jeden Thread-Zugriff erhöht.

Während beim Thread-Klassenansatz müssen Sie für jeden Thread-Zugriff eine separate Instanz erstellen. Daher wird für jede Klasseninstanz ein anderer Speicher zugewiesen und jede hat einen separaten Zähler. Der Wert bleibt gleich, was bedeutet, dass kein Inkrement erfolgt, da keine der Objektreferenzen gleich ist.

Wann soll Runnable verwendet werden?
Verwenden Sie die ausführbare Schnittstelle, wenn Sie über die Gruppe von Threads auf dieselben Ressourcen zugreifen möchten. Vermeiden Sie hier die Verwendung der Thread-Klasse, da die Erstellung mehrerer Objekte mehr Speicherplatz beansprucht und ein hoher Leistungsaufwand entsteht.

Eine Klasse, die Runnable implementiert, ist kein Thread und nur eine Klasse. Damit ein Runnable zu einem Thread wird, müssen Sie eine Thread-Instanz erstellen und sich selbst als Ziel übergeben.

In den meisten Fällen sollte die Runnable-Schnittstelle verwendet werden, wenn Sie nur die run()Methode und keine anderen Thread-Methoden überschreiben möchten . Dies ist wichtig, da Klassen nur dann in Unterklassen unterteilt werden sollten, wenn der Programmierer beabsichtigt, das grundlegende Verhalten der Klasse zu ändern oder zu verbessern.

Wenn eine Oberklasse erweitert werden muss, ist die Implementierung der Runnable-Schnittstelle besser geeignet als die Verwendung der Thread-Klasse. Weil wir eine andere Klasse erweitern können, während wir die Runnable-Schnittstelle implementieren, um einen Thread zu erstellen.

Ich hoffe das wird helfen!


40
Ihr Code ist offensichtlich falsch. Ich meine, es macht das, was es macht, aber nicht das, was Sie zeigen wollten.
zEro

38
Zur Verdeutlichung: Für den ausführbaren Fall haben Sie dieselbe ImplementsRunnable-Instanz verwendet, um mehrere Threads zu starten, während Sie für den Thread-Fall verschiedene ExtendsThread-Instanzen erstellen, was offensichtlich zu dem von Ihnen gezeigten Verhalten führt. Die 2. Hälfte Ihrer Hauptmethode sollte sein: 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(); Ist es klarer?
zEro

19
Ich verstehe Ihre Absicht noch nicht, aber mein Punkt war, dass wenn Sie mehrere Instanzen von ExtendsThread erstellen, alle 1 zurückgeben (wie Sie gezeigt haben). Sie können dieselben Ergebnisse für Runnable erzielen, indem Sie dort dasselbe tun, dh mehrere Instanzen von ImplementsRunnable erstellen.
zEro

3
@zEro Hallo, ich bin aus der Zukunft. Ist Threaddie Aussage by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instancedann falsch , da Ihre Version des Codes ebenfalls inkrementell ist ? Wenn nicht, was ist dann ein Fall, der dies demonstriert?
Böse Waschmaschine

4
Der hier veröffentlichte Code ist irreführend und kann zu Kopfschmerzen führen. Vielen Dank an @zEro für die Aufklärung.
Nikos

78

Eine Sache, von der ich überrascht bin, dass sie noch nicht erwähnt wurde, ist, dass die Implementierung RunnableIhre Klasse flexibler macht.

Wenn Sie den Thread erweitern, befindet sich die Aktion, die Sie ausführen, immer in einem Thread. Wenn Sie jedoch implementieren Runnable, muss es nicht sein. Sie können es in einem Thread ausführen oder an eine Art Executor-Service übergeben oder es einfach als Aufgabe in einer einzelnen Thread-Anwendung weitergeben (möglicherweise zu einem späteren Zeitpunkt, aber innerhalb desselben Threads). Die Optionen sind viel offener, wenn Sie sie nur verwenden, Runnableals wenn Sie sich daran binden Thread.


6
Nun, Sie können das gleiche auch mit einem ThreadObjekt tun, weilThread implements Runnable ... ;-) Aber es "fühlt sich besser an", diese Dinge mit einem zu Runnabletun, als sie mit einem zu tun Thread!
Siegi

7
Stimmt, aber Thread fügt eine Menge zusätzlicher Dinge hinzu, die Sie nicht benötigen und in vielen Fällen nicht wollen. Sie sind immer besser dran, die Schnittstelle zu implementieren, die Ihren tatsächlichen Aktivitäten entspricht.
Herms

74

Wenn Sie eine andere Klasse implementieren oder erweitern möchten, Runnableist die Schnittstelle am besten vorzuziehen. Wenn Sie nicht möchten, dass eine andere Klasse erweitert oder implementiert wird, ist die ThreadKlasse vorzuziehen.

Der häufigste Unterschied ist

Geben Sie hier die Bildbeschreibung ein

Wenn Sie unterrichten extends Thread, können Sie danach keine andere Klasse erweitern, die Sie benötigt haben. (Wie Sie wissen, erlaubt Java nicht, mehr als eine Klasse zu erben.)

Wenn Sie implements Runnablemöchten, können Sie Platz für Ihre Klasse sparen, um jede andere Klasse in Zukunft oder jetzt zu erweitern.

  • Java unterstützt nicht mehrere Vererbungen. Dies bedeutet, dass Sie nur eine Klasse in Java erweitern können. Wenn Sie also die Thread-Klasse erweitert haben, haben Sie Ihre Chance verloren und können keine andere Klasse in Java erweitern oder erben.

  • In der objektorientierten Programmierung bedeutet das Erweitern einer Klasse im Allgemeinen das Hinzufügen neuer Funktionen und das Ändern oder Verbessern von Verhaltensweisen. Wenn wir keine Änderungen an Thread vornehmen, verwenden Sie stattdessen die Runnable-Schnittstelle.

  • Die ausführbare Schnittstelle stellt eine Aufgabe dar, die entweder von einem einfachen Thread oder von Executors oder auf andere Weise ausgeführt werden kann. Daher ist die logische Trennung von Task als ausführbar als Thread eine gute Entwurfsentscheidung.

  • Wenn Sie die Aufgabe als ausführbar trennen, können Sie die Aufgabe wiederverwenden und haben auch die Freiheit, sie auf verschiedene Weise auszuführen. da Sie einen Thread nach Abschluss nicht mehr neu starten können. wieder Runnable vs Thread für Aufgabe, Runnable ist Gewinner.

  • Der Java-Designer erkennt dies und deshalb akzeptieren Executoren Runnable als Task und haben einen Worker-Thread, der diese Task ausführt.

  • Das Erben aller Thread-Methoden ist ein zusätzlicher Aufwand, nur um eine Aufgabe darzustellen, die mit Runnable problemlos ausgeführt werden kann.

Mit freundlicher Genehmigung von javarevisited.blogspot.com

Dies waren einige der bemerkenswerten Unterschiede zwischen Thread und Runnable in Java. Wenn Sie weitere Unterschiede zwischen Thread und Runnable kennen, teilen Sie diese bitte über Kommentare mit. Ich persönlich verwende Runnable over Thread für dieses Szenario und empfehle, je nach Ihren Anforderungen die Runnable- oder Callable-Schnittstelle zu verwenden.

Der signifikante Unterschied ist jedoch.

Wenn Sie extends Threadklassifizieren, erstellt jeder Ihrer Threads ein eindeutiges Objekt und ordnet es zu. Wenn Sie implements Runnabledasselbe Objekt für mehrere Threads freigeben.


73

Eigentlich ist es nicht klug , zu vergleichen Runnableund Threadmiteinander.

Diese beiden haben eine Abhängigkeit und Beziehung in Multithreading genau wie Wheel and Engine Beziehung des Kraftfahrzeugs.

Ich würde sagen, es gibt nur einen Weg für Multithreading mit zwei Schritten. Lassen Sie mich meinen Standpunkt klarstellen.

Runnable:
Bei der Implementierung interface Runnablebedeutet dies, dass Sie etwas erstellen, das sich run ablein einem anderen Thread befindet. Wenn Sie jetzt etwas erstellen, das in einem Thread ausgeführt werden kann (das in einem Thread ausgeführt werden kann), müssen Sie keinen Thread erstellen.
Die Klasse MyRunnableist also nichts anderes als eine gewöhnliche Klasse mit einer void runMethode. Und seine Objekte sind einige gewöhnliche Objekte mit nur einer Methode, rundie beim Aufruf normal ausgeführt wird. (es sei denn, wir übergeben das Objekt in einem Thread).

Thread:
class Thread Ich würde sagen, eine ganz besondere Klasse mit der Fähigkeit, einen neuen Thread zu starten, der tatsächlich Multithreading durch seine start()Methode ermöglicht.

Warum nicht weise vergleichen?
Weil wir beide für Multithreading brauchen.

Für Multithreading benötigen wir zwei Dinge:

  • Etwas, das in einem Thread ausgeführt werden kann (Runnable).
  • Etwas, das einen neuen Thread (Thread) starten kann.

Technisch und theoretisch sind beide notwendig, um einen Thread zu starten. Einer wird ausgeführt und einer wird ausgeführt (wie Wheel and Enginebei einem Kraftfahrzeug).

Aus diesem Grund können Sie einen Thread nicht starten, MyRunnablewenn Sie ihn an eine Instanz von übergeben müssen Thread.

Es ist jedoch möglich, einen Thread nur mit zu erstellen und auszuführen, class Threadda Class Threadimplementiert, Runnablesodass wir alle wissen, dass es sich Threadauch um ein RunnableInside handelt.

Schließlich Threadund Runnableergänzen sich für Multithreading nicht Konkurrenten oder Ersatz.


3
Genau! Dies sollte die akzeptierte Antwort sein. Übrigens denke ich, dass die Frage bearbeitet wurde und ThreadAkeinen Sinn mehr hat
idelvall

Die akzeptierte Antwort ist viel mehr delegiert, danke für Ihre Antwort @idelvall
Saif

Die beste Antwort! Vielen Dank!
MichaelYe

44

Sie sollten Runnable implementieren, aber wenn Sie unter Java 5 oder höher arbeiten, sollten Sie es nicht mit starten, new Threadsondern stattdessen einen ExecutorService verwenden. Weitere Informationen finden Sie unter: So implementieren Sie einfaches Threading in Java .


5
Ich würde nicht denken, dass ExecutorService so nützlich wäre, wenn Sie nur einen einzelnen Thread starten möchten.
Powerlord

1
Nach dem, was ich gelernt habe, sollte man einen Thread im Allgemeinen nicht mehr alleine starten, da das Überlassen des Executor-Dienstes alles viel kontrollierbarer macht (wie das Warten auf das Anhalten des Threads). Außerdem sehe ich in der Frage nichts, was impliziert, dass es sich um einen einzelnen Thread handelt.
Fabian Steeg

4
Was bringt es, Multithreading zu verwenden, wenn wir vorher wissen, dass es sich um einen einzelnen Thread handelt? Nehmen wir also an, wir haben mehrere Threads und diese Antwort ist wertvoll.
zEro

1
@zEro Ich bin mir ziemlich sicher, dass es einen Grund gibt, warum es nur einen Event Dispatch Thread gibt. Ich bezweifle, dass dies der einzige Fall ist, in dem es am besten ist, einen separaten Thread zu haben, aber möglicherweise nicht am besten, mehrere zu haben.
Porcoesphino

33

Ich bin kein Experte, aber ich kann mir einen Grund vorstellen, Runnable zu implementieren, anstatt Thread zu erweitern: Java unterstützt nur die Einzelvererbung, sodass Sie nur eine Klasse erweitern können.

Bearbeiten: Dies sagte ursprünglich "Die Implementierung einer Schnittstelle erfordert weniger Ressourcen." Sie müssen jedoch in beiden Fällen eine neue Thread-Instanz erstellen, sodass dies falsch war.


In Runnable können wir keine Netzwerkanrufe tätigen, oder? Da ich android.os.NetworkOnMainThreadException habe. Aber mit Thread kann ich Netzwerkanrufe machen. Bitte korrigieren Sie mich, wenn ich falsch liege.
Nabeel Thobani

@NabeelThobani Normal Java ist das egal, aber es klingt wie Android. Ich bin mit Android nicht vertraut genug, um das zu sagen.
Powerlord

1
@NabeelThobani Natürlich kannst du. Wahrscheinlich erstellen Sie mit Ihrem Runnable keinen Thread.
m0skit0

20

Ich würde sagen, es gibt einen dritten Weg:

public class Something {

    public void justAnotherMethod() { ... }

}

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

Vielleicht wird dies ein wenig durch meine jüngste starke Verwendung von Javascript und Actionscript 3 beeinflusst, aber auf diese Weise muss Ihre Klasse keine ziemlich vage Oberfläche wie implementieren Runnable.


38
Dies ist nicht wirklich ein dritter Weg. Sie implementieren Runnable immer noch, nur anonym.
Don Roby

2
@ Don Roby: Was anders ist. Dies ist häufig praktisch und Sie können Felder und endgültige lokale Variablen aus der enthaltenen Klasse / Methode verwenden.
Bart van Heukelom

6
@ BartvanHeukelom Es ist praktisch, aber nicht anders. Sie können dies mit jeder Art von verschachtelter Klasse tun, dh mit inneren Klassen, lokalen Klassen und Lambda-Ausdrücken.
Xehpuk

18

Mit der Veröffentlichung von Java 8 gibt es jetzt eine dritte Option.

Runnableist eine funktionale Schnittstelle , dh Instanzen davon können mit Lambda-Ausdrücken oder Methodenreferenzen erstellt werden.

Ihr Beispiel kann ersetzt werden durch:

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

oder wenn Sie eine ExecutorServiceund eine Methodenreferenz verwenden möchten :

executor.execute(runner::run)

Diese sind nicht nur viel kürzer als Ihre Beispiele, sondern bieten auch viele der Vorteile, die in anderen Antworten zur Verwendung von RunnableOver angegeben sind Thread, z. B. Einzelverantwortung und Verwendung von Komposition, da Sie sich nicht auf das Verhalten des Threads spezialisieren. Auf diese Weise wird auch vermieden, dass eine zusätzliche Klasse erstellt wird, wenn Sie nur eine benötigen, Runnablewie Sie es in Ihren Beispielen tun.


Diese Antwort muss erklärt werden. Nach einigem Rätsel komme ich zu dem Schluss, dass dies () -> {}die benutzerdefinierte Logik darstellen soll, die jemand benötigt? Also wäre es besser gesagt als () -> { /* Code here */ }?
ToolmakerSteve

17

Das Instanziieren einer Schnittstelle führt zu einer saubereren Trennung zwischen Ihrem Code und der Implementierung von Threads. Daher würde ich in diesem Fall lieber Runnable implementieren.


12

Jeder hier scheint zu denken, dass die Implementierung von Runnable der richtige Weg ist, und ich bin nicht wirklich anderer Meinung, aber meiner Meinung nach gibt es auch einen Grund, Thread zu erweitern. Tatsächlich haben Sie dies in Ihrem Code demonstriert.

Wenn Sie Runnable implementieren, hat die Klasse, die Runnable implementiert, keine Kontrolle über den Threadnamen. Es ist der aufrufende Code, der den Threadnamen wie folgt festlegen kann:

new Thread(myRunnable,"WhateverNameiFeelLike");

Wenn Sie jedoch Thread erweitern, können Sie dies innerhalb der Klasse selbst verwalten (genau wie in Ihrem Beispiel nennen Sie den Thread 'ThreadB'). In diesem Fall haben Sie:

A) könnte ihm einen nützlicheren Namen für Debugging-Zwecke geben

B) erzwingen, dass dieser Name für alle Instanzen dieser Klasse verwendet wird (es sei denn, Sie ignorieren die Tatsache, dass es sich um einen Thread handelt, und führen die obigen Schritte so aus, als ob es sich um eine ausführbare Datei handelt, aber wir sprechen hier auf jeden Fall über Konventionen ignoriere diese Möglichkeit, die ich fühle).

Sie können zum Beispiel sogar einen Stack-Trace seiner Erstellung nehmen und diesen als Thread-Namen verwenden. Dies mag seltsam erscheinen, kann jedoch je nach Struktur Ihres Codes für Debugging-Zwecke sehr nützlich sein.

Dies mag wie eine kleine Sache erscheinen, aber wenn Sie eine sehr komplexe Anwendung mit vielen Threads haben und plötzlich die Dinge "gestoppt" haben (entweder aus Gründen des Deadlocks oder möglicherweise aufgrund eines Fehlers in einem Netzwerkprotokoll, der geringer wäre Offensichtlich - oder aus anderen endlosen Gründen) ist es nicht immer sehr nützlich, einen Stack-Dump von Java zu erhalten, bei dem alle Threads als "Thread-1", "Thread-2" und "Thread-3" bezeichnet werden (dies hängt davon ab, wie Ihre Threads sind strukturiert und ob Sie anhand ihrer Stapelverfolgung sinnvoll erkennen können, welche welche ist - nicht immer möglich, wenn Sie Gruppen mit mehreren Threads verwenden, die alle denselben Code ausführen).

Allerdings können Sie dies natürlich auch generisch tun, indem Sie eine Erweiterung der Thread-Klasse erstellen, die ihren Namen auf einen Stack-Trace ihres Erstellungsaufrufs setzt, und diesen dann mit Ihren Runnable-Implementierungen anstelle der Standard-Java-Thread-Klasse verwenden (siehe unten) Zusätzlich zum Stack-Trace enthält der Thread-Name möglicherweise weitere kontextspezifische Informationen, die für das Debuggen nützlich sind (ein Verweis auf eine der vielen Warteschlangen oder Sockets, die beispielsweise verarbeitet werden könnten. In diesem Fall möchten Sie dies möglicherweise bevorzugen Erweitern Sie Thread speziell für diesen Fall, damit der Compiler Sie (oder andere Benutzer Ihrer Bibliotheken) zwingen kann, bestimmte Informationen (z. B. die betreffende Warteschlange / den betreffenden Socket) zur Verwendung im Namen zu übergeben.

Hier ist ein Beispiel für den generischen Thread mit dem aufrufenden Stack-Trace als Namen:

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

und hier ist ein Beispiel der Ausgabe, in der die beiden Namen verglichen werden:

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 was ist dein Punkt? Sie konnten Ihren obigen Code während der Ausführung des Threads nicht verwenden, um einen Stacktrace der Thread-Erstellung zu erhalten (stattdessen würden Sie einen einfachen Namen oder bestenfalls einen Stacktrace des Thread-Starts erhalten), aber durch Unterklassen des Threads können Sie genau das tun und erzwingen Sie benötigen sogar weitere kontextspezifische Informationen, um genauer zu verstehen, welcher Thread möglicherweise ein Problem aufweist.
AntonyM

8
Mein Punkt ist, dass "Wenn Sie Runnable implementieren, hat die Klasse, die Runnable implementiert, keine Kontrolle über den Threadnamen hat ..." offensichtlich falsch ist. Eine implementierte Klasse Runnablekann tatsächlich den Threadnamen steuern, da der Thread, in dem der Code ausgeführt wird, per Definition der aktuelle Thread ist (und jeder Code, der die Sicherheitsüberprüfungen besteht, die Kontrolle über die Threadnamen hat). Wenn man bedenkt, dass man die Hälfte seines Beitrags "omg, was ist mit Thread-Namen!" Widmet, scheint das eine ziemlich große Sache zu sein.
CHao

Der Threadname? Nichts hindert Sie daran, die Thread-Klasse zu erweitern.
RichieHH

11

Ausführbar, weil:

  • Ermöglicht der Runnable-Implementierung mehr Flexibilität beim Erweitern einer weiteren Klasse
  • Trennt den Code von der Ausführung
  • Ermöglicht das Ausführen Ihrer ausführbaren Datei aus einem Thread-Pool, dem Ereignis-Thread oder auf andere Weise in der Zukunft.

Auch wenn Sie jetzt nichts davon brauchen, können Sie es in Zukunft tun. Da das Überschreiben von Thread keinen Vorteil hat, ist Runnable eine bessere Lösung.


11

Da dies ein sehr beliebtes Thema ist und die guten Antworten überall verteilt und ausführlich behandelt werden, hielt ich es für gerechtfertigt, die guten Antworten der anderen in einer präziseren Form zusammenzufassen, damit Neulinge im Voraus einen einfachen Überblick haben:

  1. Normalerweise erweitern Sie eine Klasse, um Funktionen hinzuzufügen oder zu ändern. Also, wenn Sie nicht wollen , zu überschreiben jedes Thema Verhalten , dann Runnable verwenden.

  2. Im gleichen Licht, wenn Sie nicht brauchen zu vererben Thread Methoden können Sie , ohne dass Sie Kopf Runnable unter Verwendung.

  3. Einzelvererbung : Wenn Sie Thread erweitern, können Sie keine Erweiterung von einer anderen Klasse vornehmen. Wenn Sie dies also tun müssen, müssen Sie Runnable verwenden.

  4. Es ist ein gutes Design, Domänenlogik von technischen Mitteln zu trennen. In diesem Sinne ist es besser, eine ausführbare Aufgabe zu haben, die Ihre Aufgabe von Ihrem Läufer isoliert .

  5. Sie können dasselbe ausführbare Objekt mehrmals ausführen. Ein Thread-Objekt kann jedoch nur einmal gestartet werden. (Vielleicht der Grund, warum Executoren Runnables akzeptieren, aber keine Threads.)

  6. Wenn Sie Ihre Aufgabe als ausführbar entwickeln, haben Sie alle Flexibilität, wie Sie sie jetzt und in Zukunft verwenden können . Sie können es gleichzeitig über Executors, aber auch über Thread ausführen lassen. Und Sie können es auch weiterhin nicht gleichzeitig innerhalb desselben Threads verwenden / aufrufen, genau wie bei jedem anderen normalen Typ / Objekt.

  7. Dies erleichtert auch die Trennung von Aufgabenlogik- und Parallelitätsaspekten in Ihren Komponententests .

  8. Wenn Sie an dieser Frage interessiert sind, könnte Sie auch der Unterschied zwischen Callable und Runnable interessieren .


@Pino Ja, Thread selbst ist auch ein Runnable. Wenn Sie es jedoch erweitern, um es nur als Runnable zu verwenden, worum geht es dann? Warum nicht einfach einen einfachen Runnable ohne das gesamte Gepäck verwenden? Ich würde also argumentieren, dass Sie, wenn Sie Thread erweitern, ihn auch mit seiner Startmethode ausführen würden, die nur einmal verwendet werden kann. Das ist der Punkt, den Nidhish-Krishnan in seiner Antwort ansprechen wollte. Beachten Sie, dass meine nur eine Zusammenstellung oder kurze Zusammenfassung anderer Antworten hier ist.
Jörg

11

Der Unterschied zwischen dem Erweitern des Threads und dem Implementieren von Runnable ist:

Geben Sie hier die Bildbeschreibung ein


8

Dies wird im Tutorial zum Definieren und Starten eines Threads von Oracle erläutert :

Welche dieser Redewendungen sollten Sie verwenden? Die erste Redewendung, die ein ausführbares Objekt verwendet, ist allgemeiner, da das ausführbare Objekt eine andere Klasse als Thread unterordnen kann. Die zweite Redewendung ist in einfachen Anwendungen einfacher zu verwenden, wird jedoch durch die Tatsache eingeschränkt, dass Ihre Taskklasse ein Nachkomme von Thread sein muss. Diese Lektion konzentriert sich auf den ersten Ansatz, bei dem die ausführbare Aufgabe vom Thread-Objekt getrennt wird, das die Aufgabe ausführt. Dieser Ansatz ist nicht nur flexibler, sondern gilt auch für die später behandelten Thread-Management-APIs auf hoher Ebene.

Mit anderen Worten, die Implementierung Runnablefunktioniert in Szenarien, in denen Ihre Klasse eine andere Klasse als erweitert Thread. Java unterstützt keine Mehrfachvererbung. Außerdem ist eine Erweiterung Threadnicht möglich, wenn einige der übergeordneten Thread-Verwaltungs-APIs verwendet werden. Das einzige Szenario, in dem eine Erweiterung Threadvorzuziehen ist, ist eine kleine Anwendung, die in Zukunft nicht mehr aktualisiert werden muss. Es ist fast immer besser zu implementieren, Runnableda es flexibler ist, wenn Ihr Projekt wächst. Eine Designänderung hat keine großen Auswirkungen, da Sie viele Schnittstellen in Java implementieren können, aber nur eine Klasse erweitern können.


8

Die einfachste Erklärung wäre, indem Runnablewir implementieren, dass wir dasselbe Objekt mehreren Threads und jedem zuweisen könnenThread denselben Objektstatus und dasselbe Verhalten aufweist.

Angenommen, es gibt zwei Threads, Thread1 fügt eine Ganzzahl in ein Array ein und Thread2 nimmt Ganzzahlen aus dem Array, wenn das Array gefüllt ist. Beachten Sie, dass Thread2 den Status des Arrays kennen muss, unabhängig davon, ob Thread1 funktioniert ihn gefüllt hat oder nicht.

Durch die Implementierung Runnablekönnen Sie diese Flexibilität nutzen, um das Objekt gemeinsam zu nutzen, während extends ThreadSie für jeden Thread neue Objekte erstellen können. Daher geht jede Aktualisierung durch Thread1 an Thread2 verloren.


7

Wenn ich mich nicht irre, ist es mehr oder weniger ähnlich

Was ist der Unterschied zwischen einer Schnittstelle und einer abstrakten Klasse?

erweitert stellt " Is A " Beziehung & Schnittstelle bietet " Hat a Fähigkeit ".

Bevorzugen Sie implementiert Runnable :

  1. Wenn Sie die Thread-Klasse nicht erweitern und die Standardimplementierung der Thread-API ändern müssen
  2. Wenn Sie ein Feuer ausführen und den Befehl vergessen
  3. Wenn Sie bereits eine andere Klasse erweitern

Bevorzugen " erweitert Thread ":

  1. Wenn Sie einen dieser Threads überschreiben müssen Methoden wie auf der Oracle-Dokumentationsseite aufgeführt

Im Allgemeinen müssen Sie das Thread-Verhalten nicht überschreiben. Damit implementiert Runnable für die meisten bevorzugt.

In einem anderen Sinne mit Advanced ExecutorServiceoderThreadPoolExecutorService API mehr Flexibilität und Kontrolle.

Schauen Sie sich diese SE-Frage an:

ExecutorService gegen Casual Thread Spawner


5

Durch das Trennen der Thread-Klasse von der Runnable-Implementierung werden auch potenzielle Synchronisierungsprobleme zwischen dem Thread und der run () -Methode vermieden. Eine separate ausführbare Datei bietet im Allgemeinen mehr Flexibilität bei der Referenzierung und Ausführung von ausführbarem Code.


5

Das ist das S von SOLID : Einzelverantwortung.

Ein Thread verkörpert den laufenden Kontext (wie im Ausführungskontext: Stapelrahmen, Thread-ID usw.) der asynchronen Ausführung eines Codeteils. Dieser Code sollte idealerweise dieselbe Implementierung sein, ob synchron oder asynchron .

Wenn Sie sie in einer Implementierung bündeln, geben Sie dem resultierenden Objekt zwei nicht verwandte Ursachen für Änderungen:

  1. Thread-Behandlung in Ihrer Anwendung (dh Abfragen und Ändern des Ausführungskontexts)
  2. Algorithmus, der durch den Code implementiert wird (der ausführbare Teil)

Wenn die von Ihnen verwendete Sprache Teilklassen oder Mehrfachvererbung unterstützt, können Sie jede Ursache in einer eigenen Superklasse trennen. Sie läuft jedoch auf das Gleiche hinaus wie das Erstellen der beiden Objekte, da sich ihre Funktionssätze nicht überschneiden. Das ist für die Theorie.

In der Praxis muss ein Programm im Allgemeinen nicht komplexer als nötig sein. Wenn ein Thread an einer bestimmten Aufgabe arbeitet, ohne diese Aufgabe jemals zu ändern, macht es wahrscheinlich keinen Sinn, die Aufgaben zu getrennten Klassen zu machen, und Ihr Code bleibt einfacher.

Im Kontext von Java ist es wahrscheinlich einfacher, direkt mit eigenständigen Klassen zu beginnen und ihre Instanzen an (oder ) Instanzen zu übergeben , da die Funktion bereits vorhanden ist . Sobald Sie sich an dieses Muster gewöhnt haben, ist es nicht schwieriger zu verwenden (oder sogar zu lesen) als der einfache Fall eines ausführbaren Threads.RunnableThreadExecutor


5

Ein Grund, warum Sie eine Schnittstelle implementieren möchten, anstatt eine Basisklasse zu erweitern, besteht darin, dass Sie bereits eine andere Klasse erweitern. Sie können nur eine Klasse erweitern, aber Sie können eine beliebige Anzahl von Schnittstellen implementieren.

Wenn Sie Thread erweitern, verhindern Sie grundsätzlich, dass Ihre Logik von einem anderen Thread als "this" ausgeführt wird. Wenn Sie nur möchten, dass ein Thread Ihre Logik ausführt, ist es besser, Runnable zu implementieren.


Ja, indem Sie die Runnable-Schnittstelle so implementieren, dass Sie Ihre eigene Logik implementieren können, indem Sie eine beliebige Klasse erweitern. Deshalb wird Runnable der Thread-Klasse am meisten vorgezogen.
Akash5288

5

Wenn Sie runnable verwenden, können Sie den Speicherplatz für jede andere Klasse speichern.


5

Können wir den Grund, warum wir wollten, dass sich unsere Klasse als Klasse verhält, noch einmal besuchen? Thread ? Es gibt überhaupt keinen Grund, wir wollten nur eine Aufgabe ausführen, höchstwahrscheinlich in einem asynchronen Modus, was genau bedeutet, dass die Ausführung der Aufgabe von unserem Hauptthread und dem Hauptthread verzweigen muss, wenn sie vorzeitig beendet wird, möglicherweise wartet oder nicht für den verzweigten Pfad (Aufgabe).

Wenn dies der ganze Zweck ist, wo sehe ich dann die Notwendigkeit eines speziellen Threads? Dies kann erreicht werden, indem ein RAW-Thread aus dem Thread-Pool des Systems ausgewählt und ihm unsere Aufgabe zugewiesen wird (möglicherweise eine Instanz unserer Klasse), und das ist es.

Befolgen wir also das OOP-Konzept und schreiben Sie eine Klasse des Typs, den wir benötigen. Es gibt viele Möglichkeiten, Dinge richtig zu machen.

Wir brauchen eine Aufgabe, also schreiben Sie eine Aufgabendefinition, die auf einem Thread ausgeführt werden kann. Verwenden Sie also Runnable.

Denken Sie immer daran, dass dies implementsspeziell zum Vermitteln eines Verhaltens und extendszum Vermitteln eines Features / einer Eigenschaft verwendet wird.

Wir möchten nicht, dass die Eigenschaft des Threads, sondern dass sich unsere Klasse als eine Aufgabe verhält, die ausgeführt werden kann.


4

Ja, wenn Sie den ThreadA-Aufruf aufrufen, müssen Sie die Startmethode nicht aufrufen, und die Ausführungsmethode wird nur nach dem Aufruf der ThreadA-Klasse aufgerufen. Wenn Sie jedoch den ThreadB-Aufruf verwenden, muss der Startthread für die Aufrufausführungsmethode erforderlich sein. Wenn Sie weitere Hilfe haben, antworten Sie mir.


4

Ich finde es aus all den genannten Gründen am nützlichsten, Runnable zu verwenden, aber manchmal möchte ich Thread erweitern, damit ich meine eigene Thread-Stoppmethode erstellen und sie direkt in dem von mir erstellten Thread aufrufen kann.


4

Java unterstützt keine Mehrfachvererbung. Wenn Sie also die Thread-Klasse erweitern, wird keine andere Klasse erweitert.

Beispiel: Wenn Sie ein Applet erstellen, muss es die Applet-Klasse erweitern. Hier besteht die einzige Möglichkeit zum Erstellen eines Threads in der Implementierung der Runnable-Schnittstelle


4

Runnableist eine Schnittstelle, während Threades sich um eine Klasse handelt, die diese Schnittstelle implementiert. Aus gestalterischer Sicht sollte es eine klare Trennung zwischen der Definition einer Aufgabe und ihrer Ausführung geben. Ersteres liegt in der Verantwortung einer RunnalbeImplementierung, und letzteres ist Aufgabe der ThreadKlasse. In den meisten Fällen Runnableist die Implementierung der richtige Weg.


4

Unterschied zwischen Thread und ausführbar. Wenn wir einen Thread mit der Thread-Klasse erstellen, entspricht die Anzahl der Threads der Anzahl der von uns erstellten Objekte. Wenn wir einen Thread erstellen, indem wir die ausführbare Schnittstelle implementieren, können wir ein einzelnes Objekt zum Erstellen mehrerer Threads verwenden. Ein einzelnes Objekt wird also von mehreren Threads gemeinsam genutzt. Daher wird weniger Speicher benötigt

Also je nach Anforderung, wenn unsere Daten nicht sensibel sind. Damit es von mehreren Threads gemeinsam genutzt werden kann, können wir die Runnable-Schnittstelle verwenden.


4

Meine zwei Cent hier hinzufügen - Immer wann immer möglich verwenden implements Runnable. Im Folgenden finden Sie zwei Einschränkungen, warum Sie extends Threads nicht verwenden sollten

  1. Idealerweise sollten Sie die Thread-Klasse niemals erweitern. Die ThreadKlasse sollte gemacht werden final. Zumindest seine Methoden mögen thread.getId(). In dieser Diskussion finden Sie einen Fehler im Zusammenhang mit der Erweiterung von Threads.

  2. Diejenigen, die gerne Rätsel lösen, können einen weiteren Nebeneffekt beim Erweitern von Thread sehen. Der folgende Code gibt nicht erreichbaren Code aus, wenn niemand sie benachrichtigt.

Weitere Informationen finden Sie unter 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 ");
        }
    } 
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.