Was sind die Hauptverwendungen vonield () und wie unterscheidet es sich von join () und interrupt ()?

106

Ich bin etwas verwirrt über die Verwendung der yield()Methode in Java, insbesondere im folgenden Beispielcode. Ich habe auch gelesen, dassield () verwendet wird, um die Ausführung eines Threads zu verhindern.

Meine Fragen sind:

  1. Ich glaube, dass der folgende Code sowohl bei Verwendung yield()als auch bei Nichtverwendung zu derselben Ausgabe führt . Ist das richtig?

  2. Was sind eigentlich die Hauptverwendungen von yield()?

  3. Inwiefern yield()unterscheidet es sich von den join()und interrupt()Methoden?

Das Codebeispiel:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

Ich erhalte die gleiche Ausgabe mit dem obigen Code, sowohl mit als auch ohne yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
divz
quelle
Diese Frage sollte geschlossen werden, um zu breit zu sein .
Raedwald
Nein, es gibt nicht das gleiche Ergebnis zurück, wenn Sie haben yield()und nicht. Wenn Sie ein großes i anstelle von 5 haben, können Sie den Effekt der yield()Methode sehen.
Lakshman

Antworten:

97

Quelle: http://www.javamex.com/tutorials/threads/yield.shtml

Windows

In der Hotspot-Implementierung hat sich die Funktionsweise Thread.yield()zwischen Java 5 und Java 6 geändert.

Ruft in Java 5 Thread.yield()den Windows-API-Aufruf auf Sleep(0). Dies hat den besonderen Effekt, dass das Quantum des aktuellen Threads gelöscht und für seine Prioritätsstufe an das Ende der Warteschlange gestellt wird . Mit anderen Worten, alle ausführbaren Threads mit derselben Priorität (und diejenigen mit höherer Priorität) können ausgeführt werden, bevor dem angegebenen Thread die nächste CPU-Zeit zugewiesen wird. Wenn es schließlich neu geplant wird, wird es mit einem vollen vollen Quant zurückkommen , aber es "überträgt" keines der verbleibenden Quanten ab dem Zeitpunkt der Ausbeute. Dieses Verhalten unterscheidet sich ein wenig von einem Schlaf ungleich Null, bei dem der schlafende Thread im Allgemeinen 1 Quantenwert verliert (tatsächlich 1/3 eines 10- oder 15-ms-Ticks).

In Java 6 wurde dieses Verhalten geändert. Die Hotspot-VM wird jetzt Thread.yield()mithilfe des Windows- SwitchToThread()API-Aufrufs implementiert . Durch diesen Aufruf gibt der aktuelle Thread seine aktuelle Zeitscheibe auf , aber nicht sein gesamtes Quantum. Dies bedeutet, dass abhängig von den Prioritäten anderer Threads der nachgebende Thread in einer Unterbrechungsperiode später zurück geplant werden kann . ( Weitere Informationen zu Zeitscheiben finden Sie im Abschnitt zur Thread-Planung .)

Linux

Unter Linux ruft Hotspot einfach auf sched_yield(). Die Folgen dieses Aufrufs sind etwas anders und möglicherweise schwerwiegender als unter Windows:

  • Ein nachgegebener Thread erhält erst dann einen weiteren CPU-Slice, wenn alle anderen Threads einen CPU-Slice hatten .
  • (zumindest ab Kernel 2.6.8) Die Tatsache, dass der Thread nachgegeben hat, wird implizit von den Heuristiken des Schedulers in Bezug auf seine aktuelle CPU-Zuweisung berücksichtigt. Somit könnte einem Thread, der nachgegeben hat, implizit mehr CPU zugewiesen werden, wenn er eingeplant wird die Zukunft.

( Weitere Informationen zu Prioritäten und Planungsalgorithmen finden Sie im Abschnitt zur Thread-Planung .)

Wann verwenden yield()?

Ich würde praktisch nie sagen . Das Verhalten ist nicht standardmäßig definiert und es gibt im Allgemeinen bessere Möglichkeiten, die Aufgaben auszuführen, die Sie möglicherweise mitield () ausführen möchten:

  • Wenn Sie versuchen, nur einen Teil der CPU zu verwenden , können Sie dies auf kontrollierbarere Weise tun, indem Sie abschätzen, wie viel CPU der Thread in seinem letzten Verarbeitungsabschnitt verwendet hat, und dann einige Zeit schlafen, um dies zu kompensieren: siehe die sleep () -Methode;
  • Wenn Sie darauf warten, dass ein Prozess oder eine Ressource abgeschlossen wird oder verfügbar wird, gibt es effizientere Möglichkeiten, um dies zu erreichen, z. B. indem Sie mit join () auf den Abschluss eines anderen Threads warten und mithilfe des Wait / Notify- Mechanismus einen Thread zulassen um einem anderen zu signalisieren, dass eine Aufgabe abgeschlossen ist, oder im Idealfall, indem eines der Java 5-Parallelitätskonstrukte wie ein Semaphor oder eine Blockierungswarteschlange verwendet wird .
Sathwick
quelle
18
"verbleibendes Quantum", "gesamtes Quantum" - irgendwo auf dem Weg vergaß jemand, was das Wort "Quantum" bedeutet
kbolino
@kbolino Quantum ist das neue Atom.
Evgeni Sergeev
2
@kbolino - ... lateinisch: "so viel wie", "wie viel" . Ich sehe nicht, wie dies in irgendeiner Weise im Widerspruch zur obigen Verwendung steht. Das Wort bedeutet einfach eine beschriebene Menge von etwas, daher erscheint es mir völlig vernünftig, es in gebrauchte und verbleibende Teile zu unterteilen.
Periata Breatta
@PeriataBreatta Ich denke, es ist sinnvoller, wenn Sie mit dem Wort außerhalb der Physik vertraut sind. Die physikalische Definition war die einzige, die ich kannte.
Kbolino
Ich habe diese Frage mit einem Kopfgeld versehen, um diese Antwort für 7, 8, 9 zu aktualisieren. Bearbeiten Sie sie mit den aktuellen Informationen zu 7,8 und 8, und Sie erhalten das Kopfgeld.
40

Ich sehe, dass die Frage mit einem Kopfgeld reaktiviert wurde und nun gefragt wird, wozu die praktischen Verwendungszwecke yielddienen. Ich werde ein Beispiel aus meiner Erfahrung geben.

Wie wir wissen, yieldwird der aufrufende Thread gezwungen, den Prozessor aufzugeben, auf dem er ausgeführt wird, damit die Ausführung eines anderen Threads geplant werden kann. Dies ist nützlich, wenn der aktuelle Thread seine Arbeit vorerst beendet hat, aber schnell zum Anfang der Warteschlange zurückkehren und prüfen möchte, ob sich eine Bedingung geändert hat. Wie unterscheidet sich das von einer Bedingungsvariablen? yieldermöglicht es dem Thread, viel schneller in einen laufenden Zustand zurückzukehren. Beim Warten auf eine Bedingungsvariable wird der Thread angehalten und muss warten, bis ein anderer Thread signalisiert, dass er fortgesetzt werden soll.yieldIm Grunde heißt es: "Erlaube, dass ein anderer Thread ausgeführt wird, aber erlaube mir, sehr bald wieder an die Arbeit zu gehen, da ich erwarte, dass sich in meinem Zustand sehr schnell etwas ändert." Dies deutet auf ein starkes Spinnen hin, bei dem sich ein Zustand schnell ändern kann, das Anhalten des Threads jedoch zu einem großen Leistungseinbruch führen würde.

Aber genug Geplapper, hier ist ein konkretes Beispiel: das Wellenfront-Parallelmuster. Ein grundlegendes Beispiel für dieses Problem ist die Berechnung der einzelnen "Inseln" von 1s in einem zweidimensionalen Array, das mit 0s und 1s gefüllt ist. Eine "Insel" ist eine Gruppe von Zellen, die entweder vertikal oder horizontal nebeneinander liegen:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Hier haben wir zwei Inseln von 1s: oben links und unten rechts.

Eine einfache Lösung besteht darin, einen ersten Durchgang über das gesamte Array durchzuführen und die 1-Werte durch einen inkrementierenden Zähler zu ersetzen, sodass am Ende jede 1 durch ihre Sequenznummer in der Hauptreihenfolge ersetzt wurde:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

Im nächsten Schritt wird jeder Wert durch das Minimum zwischen sich und den Werten seiner Nachbarn ersetzt:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Wir können jetzt leicht feststellen, dass wir zwei Inseln haben.

Der Teil, den wir parallel ausführen möchten, ist der Schritt, in dem wir die Mindestwerte berechnen. Ohne zu sehr ins Detail zu gehen, erhält jeder Thread verschachtelte Zeilen und stützt sich auf die Werte, die von dem Thread berechnet wurden, der die obige Zeile verarbeitet. Daher muss jeder Thread etwas hinter dem Thread zurückbleiben, der die vorherige Zeile verarbeitet, muss aber auch innerhalb einer angemessenen Zeit mithalten. Weitere Details und eine Implementierung werden von mir in diesem Dokument vorgestellt . Beachten Sie, dass die Verwendung sleep(0)mehr oder weniger dem C-Äquivalent von entspricht yield.

In diesem Fall yieldwurde verwendet, um jeden Thread nacheinander zum Anhalten zu zwingen. Da jedoch der Thread, der die benachbarte Zeile verarbeitet, in der Zwischenzeit sehr schnell vorrücken würde, würde sich eine Bedingungsvariable als katastrophale Wahl erweisen.

Wie Sie sehen können, yieldhandelt es sich um eine ziemlich feinkörnige Optimierung. Wenn Sie es an der falschen Stelle verwenden, z. B. wenn Sie auf einen Zustand warten, der sich selten ändert, wird die CPU übermäßig ausgelastet.

Entschuldigung für das lange Geschwätz, hoffe ich habe mich klar ausgedrückt.

Tudor
quelle
1
IIUC, was Sie in dem Dokument präsentieren, ist die Idee, dass es in diesem Fall effizienter ist, beschäftigt zu warten und aufzurufen, yieldwenn die Bedingung nicht erfüllt ist, um den anderen Threads die Möglichkeit zu geben, mit der Berechnung fortzufahren, anstatt mehr zu verwenden. Level-Synchronisationsprimitive, richtig?
Petr Pudlák
3
@Petr Pudlák: Ja. Ich habe dies mit der Verwendung von Thread-Signalen verglichen und der Leistungsunterschied war in diesem Fall enorm. Da die Bedingung sehr schnell erfüllt werden kann (dies ist das Hauptproblem), sind Bedingungsvariablen zu langsam, da der Thread vom Betriebssystem angehalten wird, anstatt die CPU für eine sehr kurze Zeit mit aufzugeben yield.
Tudor
@Tudor tolle Erklärung!
Entwickler Marius Žilėnas
1
"Beachten Sie die Verwendung von Schlaf (0), die mehr oder weniger dem C-Äquivalent von Ertrag entspricht." .. Nun, wenn Sie mit Java schlafen (0) möchten, warum würden Sie das nicht einfach verwenden? Thread.sleep () ist eine Sache, die bereits existiert. Ich bin nicht sicher, ob diese Antwort eine Begründung dafür liefert, warum man Thread.yield () anstelle von Thread.sleep (0) verwenden würde; Es gibt auch einen vorhandenen Thread, der erklärt, warum sie unterschiedlich sind.
Eis
@eis: Thread.sleep (0) vs Thread.yield () würde den Rahmen dieser Antwort sprengen. Ich erwähnte Thread.sleep (0) nur für Leute, die nach einem engen Äquivalent in C suchen. Die Frage betraf die Verwendung von Thread.yield ().
Tudor
12

Über die Unterschiede zwischen yield(), interrupt()und join()- in der Regel nicht nur in Java:

  1. Nachgeben : Nachgeben bedeutet wörtlich loslassen, aufgeben, sich ergeben. Ein nachgebender Thread teilt dem Betriebssystem (oder der virtuellen Maschine oder was nicht) mit, dass er bereit ist, stattdessen andere Threads planen zu lassen. Dies zeigt an, dass es nicht zu kritisch ist. Es ist jedoch nur ein Hinweis und garantiert keine Wirkung.
  2. Joining : Wenn mehrere Threads auf einem Handle, Token oder einer Entität 'beitreten', warten alle, bis alle anderen relevanten Threads die Ausführung abgeschlossen haben (vollständig oder bis zu ihrem eigenen entsprechenden Join). Das bedeutet, dass eine Reihe von Threads alle ihre Aufgaben erledigt haben. Dann kann jeder dieser Threads so geplant werden, dass andere Arbeiten fortgesetzt werden, wobei davon ausgegangen werden kann, dass alle diese Aufgaben tatsächlich abgeschlossen sind. (Nicht zu verwechseln mit SQL Joins!)
  3. Unterbrechung : Wird von einem Thread verwendet, um einen anderen Thread zu "stupsen", der sich im Ruhezustand befindet, wartet oder beitritt - damit er wieder ausgeführt werden kann, möglicherweise mit dem Hinweis, dass er unterbrochen wurde. (Nicht zu verwechseln mit Hardware-Interrupts!)

Speziell für Java siehe

  1. Beitritt:

    Wie benutze ich Thread.join? (hier auf StackOverflow)

    Wann kann man Threads verbinden?

  2. Nachgeben:

  3. Unterbrechen:

    Ist Thread.interrupt () böse? (hier auf StackOverflow)

einpoklum
quelle
Was meinst du mit dem Verbinden eines Handles oder Tokens? Die Methoden wait () und notify () befinden sich für Object, sodass ein Benutzer auf ein beliebiges Objekt warten kann. Aber join () scheint weniger abstrakt zu sein und muss für den spezifischen Thread aufgerufen werden, den Sie beenden möchten, bevor Sie fortfahren ... nicht wahr?
spaaarky21
@ spaaarky21: Ich meinte allgemein, nicht unbedingt in Java. Außerdem handelt es sich bei a wait()nicht um eine Verknüpfung, sondern um eine Sperre für das Objekt, das der aufrufende Thread zu erfassen versucht. Er wartet, bis die Sperre von anderen freigegeben und vom Thread erfasst wurde. Meine Antwort wurde entsprechend angepasst.
Einpoklum
10

Erstens ist die eigentliche Beschreibung

Bewirkt, dass das aktuell ausgeführte Thread-Objekt vorübergehend angehalten wird und andere Threads ausgeführt werden können.

Nun ist es sehr wahrscheinlich, dass Ihr Hauptthread die Schleife fünfmal ausführt, bevor die runMethode des neuen Threads ausgeführt wird, sodass alle Aufrufe erst ausgeführt yieldwerden, nachdem die Schleife im Hauptthread ausgeführt wurde.

joinstoppt den aktuellen Thread, bis die join()Ausführung des Threads, mit dem aufgerufen wird, abgeschlossen ist.

interruptunterbricht den Thread, für den es aufgerufen wird, und verursacht eine InterruptedException .

yield Ermöglicht einen Kontextwechsel zu anderen Threads, sodass dieser Thread nicht die gesamte CPU-Auslastung des Prozesses beansprucht.

MByD
quelle
+1. Beachten Sie auch, dass nach dem Aufruf vonield () immer noch nicht garantiert werden kann, dass derselbe Thread bei einem Pool von Threads gleicher Priorität nicht erneut zur Ausführung ausgewählt wird.
Andrew Fielden
Allerdings SwitchToThread()Anruf ist besser als Sleep (0) , und dies soll ein Fehler in Java sein :)
Петър Петров
4

Die aktuellen Antworten sind veraltet und müssen aufgrund der jüngsten Änderungen überarbeitet werden.

Es gibt keinen praktischen Unterschied Thread.yield()zwischen Java-Versionen seit 6 bis 9.

TL; DR;

Schlussfolgerungen basierend auf OpenJDK-Quellcode ( http://hg.openjdk.java.net/ ).

Wenn die HotSpot-Unterstützung von USDT-Sonden (Informationen zur Systemverfolgung werden im dtrace-Handbuch beschrieben ) und der JVM-Eigenschaft nicht berücksichtigt wird, ist der ConvertYieldToSleepQuellcode von yield()nahezu identisch. Siehe Erklärung unten.

Java 9 :

Thread.yield()ruft die betriebssystemspezifische Methode auf os::naked_yield():
Unter Linux:

void os::naked_yield() {
    sched_yield();
}

Unter Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 und früher:

Thread.yield()ruft die betriebssystemspezifische Methode auf os::yield():
Unter Linux:

void os::yield() {
    sched_yield();
}

Unter Windows:

void os::yield() {  os::NakedYield(); }

Wie Sie sehen können, ist Thread.yeald()Linux unter Linux für alle Java-Versionen identisch.
Sehen wir uns Windows os::NakedYield()von JDK 8 an:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

Der Unterschied zwischen Java 9 und Java 8 bei der zusätzlichen Überprüfung der Existenz der Win32-API- SwitchToThread()Methode. Für Java 6 ist derselbe Code vorhanden. Der
Quellcode von os::NakedYield()in JDK 7 unterscheidet sich geringfügig, weist jedoch dasselbe Verhalten auf:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

Die zusätzliche Prüfung wurde aufgrund der SwitchToThread()Methode gelöscht, die seit Windows XP und Windows Server 2003 verfügbar ist (siehe msdn-Hinweise ).

Gregory.K
quelle
2

Was sind eigentlich die Hauptverwendungen vonield ()?

Yield schlägt der CPU vor, den aktuellen Thread anzuhalten und Threads mit höherer Priorität auszuführen. Mit anderen Worten: Zuweisen eines Werts mit niedriger Priorität zum aktuellen Thread, um Platz für kritischere Threads zu lassen.

Ich glaube, der folgende Code führt zu derselben Ausgabe, sowohl bei Verwendung vonield () als auch bei Nichtverwendung. Ist das richtig?

NEIN, die beiden führen zu unterschiedlichen Ergebnissen. Ohne ein Yield () führt der Thread, sobald er die Kontrolle erhält, die 'Inside Run'-Schleife auf einmal aus. Bei einem Yield () druckt der Thread jedoch, sobald er die Kontrolle erhält, den 'Inside Run' einmal und übergibt ihn dann gegebenenfalls an einen anderen Thread. Wenn kein Thread ansteht, wird dieser Thread erneut fortgesetzt. Jedes Mal, wenn "Inside Run" ausgeführt wird, wird nach anderen Threads gesucht, die ausgeführt werden sollen. Wenn kein Thread verfügbar ist, wird der aktuelle Thread weiterhin ausgeführt.

Inwiefern unterscheidet sich yield () von den Methoden join () und interrupt ()?

Yield () dient dazu, anderen wichtigen Threads Platz zu geben, join () wartet darauf, dass ein anderer Thread seine Ausführung abschließt, und Interrupt () dient dazu, einen aktuell ausgeführten Thread zu unterbrechen, um etwas anderes zu tun.

abbas
quelle
Wollten Sie nur bestätigen, ob diese Aussage zutrifft Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Bitte klären Sie.
Abdullah Khan
0

Thread.yield()Bewirkt, dass der Thread vom Status "Laufen" in den Status "Ausführbar" wechselt. Hinweis: Der Thread wird nicht in den Status "Warten" versetzt.

Prashant Gunjal
quelle
@PJMeisch, Es gibt keinen RUNNINGStatus für java.lang.ThreadInstanzen. Dies schließt jedoch einen nativen "laufenden" Status für den nativen Thread nicht aus, für den eine ThreadInstanz ein Proxy ist.
Solomon Slow
-1

Thread.yield ()

Wenn wir die Thread.yield () -Methode aufrufen, hält der Thread-Scheduler den aktuell ausgeführten Thread im Status "Ausführbar" und wählt einen anderen Thread mit gleicher oder höherer Priorität aus. Wenn es keinen Thread mit gleicher und höherer Priorität gibt, wird der aufrufende Yield () -Thread neu geplant. Denken Sie daran, dass die Ertragsmethode den Thread nicht in den Status Warten oder Blockiert versetzt. Es kann nur ein Thread vom laufenden Status zum ausführbaren Status erstellt werden.

beitreten()

Wenn der Join von einer Thread-Instanz aufgerufen wird, weist dieser Thread den aktuell ausgeführten Thread an, zu warten, bis der Joining-Thread abgeschlossen ist. Join wird in Situationen verwendet, in denen eine Aufgabe abgeschlossen sein sollte, bevor die aktuelle Aufgabe beendet wird.

Prashant Kumar
quelle
-4

Ausbeute () wird hauptsächlich verwendet, um eine Multithreading-Anwendung anzuhalten.

Alle diese Methodenunterschiede sind :ield () hält den Thread an, während ein anderer Thread ausgeführt wird, und kehrt nach Abschluss dieses Threads zurück. join () bringt den Anfang der Threads zusammen, die bis zum Ende ausgeführt werden, und eines anderen Threads, der ausgeführt wird, nachdem dieser Thread ausgeführt wurde beendet, Interrupt () stoppt die Ausführung eines Threads für eine Weile.

K. Hughley
quelle
Vielen Dank für Ihre Antwort. Es wird jedoch nur wiederholt, was die anderen Antworten bereits ausführlich beschreiben. Ich biete das Kopfgeld für geeignete Anwendungsfälle an, in denen yieldes verwendet werden sollte.
Petr Pudlák