Was ist der Unterschied zwischen Deadlock und Livelock?

Antworten:

398

Entnommen aus http://en.wikipedia.org/wiki/Deadlock :

Beim gleichzeitigen Rechnen ist ein Deadlock ein Zustand, in dem jedes Mitglied einer Gruppe von Aktionen darauf wartet, dass ein anderes Mitglied eine Sperre aufhebt

Ein Livelock ähnelt einem Deadlock, mit der Ausnahme, dass sich die Zustände der Prozesse, die an dem Livelock beteiligt sind, ständig in Bezug aufeinander ändern, ohne dass Fortschritte erzielt werden. Livelock ist ein Sonderfall des Ressourcenmangels. Die allgemeine Definition besagt nur, dass ein bestimmter Prozess nicht fortschreitet.

Ein reales Beispiel für Viehzucht ist, wenn sich zwei Menschen in einem engen Korridor treffen und jeder versucht, höflich zu sein, indem er sich zur Seite bewegt, um den anderen passieren zu lassen. Am Ende schwanken sie jedoch hin und her, ohne Fortschritte zu machen, weil sie sich beide wiederholt bewegen auf die gleiche Weise zur gleichen Zeit.

Livelock ist ein Risiko bei einigen Algorithmen, die Deadlocks erkennen und beheben. Wenn mehr als ein Prozess aktiv wird, kann der Deadlock-Erkennungsalgorithmus wiederholt ausgelöst werden. Dies kann vermieden werden, indem sichergestellt wird, dass nur ein Prozess (zufällig oder nach Priorität ausgewählt) Maßnahmen ergreift.

mah
quelle
8
Ich habe es bereits gefunden, aber sie haben dort keine Beispiele, wie Sie sehen konnten, trotzdem danke
macindows
61
Ich werde kein Codebeispiel bereitstellen, sondern zwei Prozesse betrachten, die jeweils auf eine Ressource warten, die der andere hat, aber nicht blockierend warten. Wenn jeder erfährt, dass er nicht fortfahren kann, gibt er seine gehaltene Ressource frei und schläft 30 Sekunden lang. Dann ruft er seine ursprüngliche Ressource ab und versucht anschließend, die Ressource zu speichern, die der andere Prozess gehalten, dann verlassen und erneut angefordert hat. Da beide Prozesse versuchen (nur schlecht) damit umzugehen, ist dies ein Livelock.
Mah
4
Können
32
Ein Deadlock-Beispiel ist viel einfacher ... Nehmen Sie zwei Prozesse A und B an und jeder möchte die Ressource r1 und die Ressource r2. Angenommen, A empfängt (oder hat bereits) r1 und B empfängt (oder hat bereits) r2. Jetzt versucht jeder, die Ressource des anderen ohne Zeitüberschreitung abzurufen. A ist blockiert, weil B r2 hält, und B ist blockiert, weil A r1 hält. Jeder Prozess ist blockiert und kann daher die gewünschte Ressource nicht freigeben, was zu einem Deadlock führt.
Mah
2
Im Kontext des Transaktionsspeichers gibt es ein großartiges Video, das Deadlock und Livelock demonstriert: youtube.com/watch?v=_IxsOEEzf-c
BlackVegetable
78

Livelock

Ein Thread reagiert häufig auf die Aktion eines anderen Threads. Wenn die Aktion des anderen Threads auch eine Antwort auf die Aktion eines anderen Threads ist, kann dies zu einem Livelock führen.

Wie beim Deadlock können Livelock-Threads keine weiteren Fortschritte erzielen . Die Threads sind jedoch nicht blockiert - sie sind einfach zu beschäftigt, um aufeinander zu reagieren, um die Arbeit fortzusetzen . Dies ist vergleichbar mit zwei Personen, die versuchen, sich in einem Korridor zu überholen: Alphonse bewegt sich zu seiner Linken, um Gaston passieren zu lassen, während Gaston sich zu seiner Rechten bewegt, um Alphonse passieren zu lassen. Als Alphonse sieht, dass sie sich immer noch gegenseitig blockieren, bewegt er sich zu seiner Rechten, während Gaston sich zu seiner Linken bewegt. Sie blockieren sich immer noch und so weiter ...

Der Hauptunterschied zwischen Livelock und Deadlock besteht darin, dass Threads nicht blockiert werden, sondern kontinuierlich versuchen, aufeinander zu reagieren.

In diesem Bild versuchen beide Kreise (Threads oder Prozesse), dem anderen Raum zu geben, indem sie sich nach links und rechts bewegen. Aber sie können sich nicht weiter bewegen.

Geben Sie hier die Bildbeschreibung ein

Buru
quelle
Codebeispiele
Yauhen Yakimovich
1
Dieses Ding hat einen Namen. Ein Slangwort vielleicht, aber immer noch: schlumperdink : P
John Red
64

Alle Inhalte und Beispiele hier stammen von

Betriebssysteme: Interna und Konstruktionsprinzipien
William Stallings
8º Edition

Deadlock : Eine Situation, in der zwei oder mehr Prozesse nicht fortgesetzt werden können, weil jeder darauf wartet, dass der andere etwas unternimmt.

Betrachten Sie beispielsweise zwei Prozesse, P1 und P2, und zwei Ressourcen, R1 und R2. Angenommen, jeder Prozess benötigt Zugriff auf beide Ressourcen, um einen Teil seiner Funktion auszuführen. Dann ist folgende Situation möglich: Das Betriebssystem weist P2 R1 und R2 P1 zu. Jeder Prozess wartet auf eine der beiden Ressourcen. Keiner von beiden gibt die Ressource frei, die er bereits besitzt, bis er die andere Ressource erworben und die Funktion ausgeführt hat, die beide Ressourcen erfordert. Die beiden Prozesse sind festgefahren

Livelock : Eine Situation, in der zwei oder mehr Prozesse ihren Zustand als Reaktion auf Änderungen in den anderen Prozessen kontinuierlich ändern, ohne nützliche Arbeit zu leisten:

Hunger : Eine Situation, in der ein ausführbarer Prozess vom Scheduler auf unbestimmte Zeit übersehen wird. Obwohl es fortfahren kann, wird es nie ausgewählt.

Angenommen, drei Prozesse (P1, P2, P3) erfordern jeweils einen periodischen Zugriff auf die Ressource R. Betrachten Sie die Situation, in der P1 im Besitz der Ressource ist und sowohl P2 als auch P3 verzögert sind und auf diese Ressource warten. Wenn P1 seinen kritischen Abschnitt verlässt, sollte entweder P2 oder P3 Zugriff auf R gewährt werden. Angenommen, das Betriebssystem gewährt Zugriff auf P3 und P1 benötigt erneut Zugriff, bevor P3 seinen kritischen Abschnitt abschließt. Wenn das Betriebssystem nach Abschluss von P3 Zugriff auf P1 gewährt und anschließend abwechselnd Zugriff auf P1 und P3 gewährt, kann P2 auf unbestimmte Zeit der Zugriff auf die Ressource verweigert werden, obwohl keine Deadlock-Situation vorliegt.

ANHANG A - THEMEN DER KONKURRENZ

Deadlock-Beispiel

Wenn beide Prozesse ihre Flags auf true setzen, bevor einer die while-Anweisung ausgeführt hat, wird jeder denken, dass der andere seinen kritischen Abschnitt betreten hat, was zu einem Deadlock führt.

/* PROCESS 0 */
flag[0] = true;            // <- get lock 0
while (flag[1])            // <- is lock 1 free?
    /* do nothing */;      // <- no? so I wait 1 second, for example
                           // and test again.
                           // on more sophisticated setups we can ask
                           // to be woken when lock 1 is freed
/* critical section*/;     // <- do what we need (this will never happen)
flag[0] = false;           // <- releasing our lock

 /* PROCESS 1 */
flag[1] = true;
while (flag[0])
    /* do nothing */;
/* critical section*/;
flag[1] = false;

Livelock Beispiel

/* PROCESS 0 */
flag[0] = true;          // <- get lock 0
while (flag[1]){         
    flag[0] = false;     // <- instead of sleeping, we do useless work
                         //    needed by the lock mechanism
    /*delay */;          // <- wait for a second
    flag[0] = true;      // <- and restart useless work again.
}
/*critical section*/;    // <- do what we need (this will never happen)
flag[0] = false; 

/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
    flag[1] = false;
    /*delay */;
    flag[1] = true;
}
/* critical section*/;
flag[1] = false;

[...] betrachten die folgende Abfolge von Ereignissen:

  • P0 setzt das Flag [0] auf true.
  • P1 setzt Flag [1] auf true.
  • P0 prüft Flag [1].
  • P1 prüft Flag [0].
  • P0 setzt das Flag [0] auf false.
  • P1 setzt Flag [1] auf false.
  • P0 setzt das Flag [0] auf true.
  • P1 setzt Flag [1] auf true.

Diese Sequenz kann auf unbestimmte Zeit verlängert werden, und keiner der Prozesse kann in seinen kritischen Abschnitt eintreten. Genau genommen ist dies kein Deadlock , da jede Änderung der relativen Geschwindigkeit der beiden Prozesse diesen Zyklus unterbricht und es einem ermöglicht, den kritischen Abschnitt zu betreten. Dieser Zustand wird als Viehbestand bezeichnet . Denken Sie daran, dass ein Deadlock auftritt, wenn eine Reihe von Prozessen ihre kritischen Abschnitte eingeben möchte, aber kein Prozess erfolgreich sein kann. Mit livelock gibt es mögliche Ausführungssequenzen, die erfolgreich sind, aber es ist auch möglich, eine oder mehrere Ausführungssequenzen zu beschreiben, in denen kein Prozess jemals in seinen kritischen Abschnitt eintritt.

Kein Inhalt mehr aus dem Buch.

Und was ist mit Spinlocks?

Spinlock ist eine Technik, um die Kosten des Betriebssystemsperrmechanismus zu vermeiden. Normalerweise würden Sie Folgendes tun:

try
{
   lock = beginLock();
   doSomething();
}
finally
{
   endLock();
}

Ein Problem tritt auf, wenn es beginLock()viel mehr kostet als doSomething(). Stellen Sie sich sehr übertrieben vor, was passiert, wenn die beginLockKosten 1 Sekunde betragen, aber doSomethingnur 1 Millisekunde kosten.

In diesem Fall würden Sie vermeiden, 1 Sekunde lang behindert zu werden, wenn Sie 1 Millisekunde warten würden.

Warum beginLockwürde das so viel kosten? Wenn die Sperre frei ist, kostet sie nicht viel (siehe https://stackoverflow.com/a/49712993/5397116 ), aber wenn die Sperre nicht frei ist, "friert" das Betriebssystem Ihren Thread ein, richten Sie einen Mechanismus ein, um Sie zu wecken Wenn das Schloss freigegeben ist, wecken Sie Sie in Zukunft erneut.

All dies ist viel teurer als einige Schleifen, die das Schloss überprüfen. Deshalb ist es manchmal besser, einen "Spinlock" zu machen.

Zum Beispiel:

void beginSpinLock(lock)
{
   if(lock) loopFor(1 milliseconds);
   else 
   {
     lock = true;
     return;
   }

   if(lock) loopFor(2 milliseconds);
   else 
   {
     lock = true;
     return;
   }

   // important is that the part above never 
   // cause the thread to sleep.
   // It is "burning" the time slice of this thread.
   // Hopefully for good.

   // some implementations fallback to OS lock mechanism
   // after a few tries
   if(lock) return beginLock(lock);
   else 
   {
     lock = true;
     return;
   }
}

Wenn Ihre Implementierung nicht vorsichtig ist, können Sie auf Livelock fallen und die gesamte CPU für den Sperrmechanismus ausgeben.

Siehe auch:

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
Ist meine Spin-Lock-Implementierung korrekt und optimal?

Zusammenfassung :

Deadlock : Situation, in der niemand Fortschritte macht, nichts tut (schlafen, warten usw.). Die CPU-Auslastung ist gering.

Livelock : Situation, in der niemand Fortschritte macht, aber die CPU für den Sperrmechanismus und nicht für Ihre Berechnung aufgewendet wird.

Hunger: Situation, in der ein Prozess nie die Chance bekommt zu rennen; durch reines Pech oder durch einen Teil seines Eigentums (zum Beispiel niedrige Priorität);

Spinlock : Technik zur Vermeidung der Kosten, die darauf warten, dass das Schloss freigegeben wird.

Daniel Frederico Lins Leite
quelle
Sir, das Beispiel, das Sie für Deadlock gegeben haben, ist tatsächlich ein Beispiel für Spinlock. Ein Deadlock tritt auf, wenn eine Reihe von Prozessen blockiert werden, die sich nicht im Bereitschafts- oder Betriebszustand befinden und auf einige Ressourcen warten. In unserem Beispiel führt jeder eine Aufgabe aus, dh er überprüft den Zustand immer wieder. Korrigieren Sie mich, wenn ich falsch liege.
Vinay Yadav
Das Beispiel ist so minimal, dass es eine Chance für diese Interpretation gibt, deshalb habe ich es verbessert, um etwas deutlicher über ihren Unterschied zu sprechen. Ich hoffe, das hilft.
Daniel Frederico Lins Leite
Vielen Dank für das Hinzufügen von Spinlocks. Spinlocks sind Ihrer Meinung nach eine Technik, und Sie haben sie auch gerechtfertigt, und ich habe verstanden. Aber was ist mit diesem Problem der Prioritätsinversion, wenn sich ein Prozess P1 im kritischen Bereich befindet und ein anderer Prozess P2 mit hoher Priorität geplant wird, bevor P1 vorbelegt wird? In diesem Fall ist die CPU mit P2 und unser Synchronisationsmechanismus mit P1. Dies wird als Spinlock als P1 in ist bereit Zustand und P2 ist in Laufzustand. Hier ist Spinlock ein Problem. Bekomme ich die Dinge richtig? Ich bin nicht in der Lage, die Feinheiten richtig zu machen. Bitte helfen Sie
Vinay Yadav
Mein Vorschlag an Sie ist, eine weitere Frage zu erstellen, in der Ihr Problem klarer dargelegt wird. Wenn Sie sich nun im "Benutzerbereich" befinden und sich P1 in einer kritischen Sitzung befindet, die mit einem SpinLock geschützt ist, der mit einer Endlosschleife implementiert ist und dessen Vorrang besteht. dann wird P2 versuchen, es zu betreten, wird fehlschlagen und seine gesamte Zeitscheibe verbrennen. Sie haben ein Livelock erstellt (eine CPU befindet sich zu 100%). (Eine schlechte Verwendung wäre es, ein Synchronisierungs-E / A mit Spinlock zu schützen. Sie können dieses Beispiel leicht ausprobieren.) Im "Kernel Space" kann Ihnen dieser Hinweis möglicherweise helfen: lxr.linux.no/linux+v3.6.6/Documentation/…
Daniel Frederico Lins Leite
Vielen Dank für die Klarstellung. Wie auch immer, Ihre Antwort war im Gegensatz zu anderen sehr beschreibend und hilfreich
Vinay Yadav
13

DEADLOCK Deadlock ist eine Bedingung, bei der eine Aufgabe auf unbestimmte Zeit auf Bedingungen wartet, die niemals erfüllt werden können - Aufgabe beansprucht die ausschließliche Kontrolle über gemeinsam genutzte Ressourcen - Aufgabe hält Ressourcen, während auf die Freigabe anderer Ressourcen gewartet wird - Aufgaben können nicht gezwungen werden, Ressourcen freizugeben - ein zirkuläres Warten Bedingung besteht

LIVELOCK Livelock-Bedingungen können auftreten, wenn zwei oder mehr Aufgaben von einer Ressource abhängen und diese verwenden. Dies führt zu einer zirkulären Abhängigkeitsbedingung, bei der diese Aufgaben für immer ausgeführt werden, wodurch die Ausführung aller Aufgaben mit niedrigerer Priorität blockiert wird (bei diesen Aufgaben mit niedrigerer Priorität tritt eine Bedingung auf, die als Hunger bezeichnet wird).

Deepak Lamichhane
quelle
Wenn die "Livelocked" -Aufgaben Protokollen zur Ressourcenarbitrierung folgen, die "Backoff" -Verzögerungen enthalten, und die meiste Zeit im Ruhezustand verbringen, werden andere Aufgaben nicht ausgehungert.
Greggo
8

Vielleicht veranschaulichen diese beiden Beispiele den Unterschied zwischen einem Deadlock und einem Livelock:


Java-Beispiel für einen Deadlock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockSample {

    private static final Lock lock1 = new ReentrantLock(true);
    private static final Lock lock2 = new ReentrantLock(true);

    public static void main(String[] args) {
        Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
        Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
        threadA.start();
        threadB.start();
    }

    public static void doA() {
        System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
        lock1.lock();
        System.out.println(Thread.currentThread().getName() + " : holds lock 1");

        try {
            System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
            lock2.lock();
            System.out.println(Thread.currentThread().getName() + " : holds lock 2");

            try {
                System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
            } finally {
                lock2.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
            }
        } finally {
            lock1.unlock();
            System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
        }
    }

    public static void doB() {
        System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
        lock2.lock();
        System.out.println(Thread.currentThread().getName() + " : holds lock 2");

        try {
            System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
            lock1.lock();
            System.out.println(Thread.currentThread().getName() + " : holds lock 1");

            try {
                System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
            } finally {
                lock1.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
            }
        } finally {
            lock2.unlock();
            System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
        }
    }
}

Beispielausgabe:

Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2

Java-Beispiel für ein Livelock:


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LivelockSample {

    private static final Lock lock1 = new ReentrantLock(true);
    private static final Lock lock2 = new ReentrantLock(true);

    public static void main(String[] args) {
        Thread threadA = new Thread(LivelockSample::doA, "Thread A");
        Thread threadB = new Thread(LivelockSample::doB, "Thread B");
        threadA.start();
        threadB.start();
    }

    public static void doA() {
        try {
            while (!lock1.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
                Thread.sleep(100);
            }
            System.out.println(Thread.currentThread().getName() + " : holds lock 1");

            try {
                while (!lock2.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
                    Thread.sleep(100);
                }
                System.out.println(Thread.currentThread().getName() + " : holds lock 2");

                try {
                    System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
                } finally {
                    lock2.unlock();
                    System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
                }
            } finally {
                lock1.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
            }
        } catch (InterruptedException e) {
            // can be ignored here for this sample
        }
    }

    public static void doB() {
        try {
            while (!lock2.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
                Thread.sleep(100);
            }
            System.out.println(Thread.currentThread().getName() + " : holds lock 2");

            try {
                while (!lock1.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
                    Thread.sleep(100);
                }
                System.out.println(Thread.currentThread().getName() + " : holds lock 1");

                try {
                    System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
                } finally {
                    lock1.unlock();
                    System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
                }
            } finally {
                lock2.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
            }
        } catch (InterruptedException e) {
            // can be ignored here for this sample
        }
    }
}

Beispielausgabe:

Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...

Beide Beispiele zwingen die Fäden, die Schlösser in unterschiedlicher Reihenfolge zu erwerben. Während der Deadlock auf das andere Schloss wartet, wartet das Livelock nicht wirklich - es versucht verzweifelt, das Schloss zu erwerben, ohne die Chance zu haben, es zu bekommen. Jeder Versuch verbraucht CPU-Zyklen.

mmirwaldt
quelle
Der Code ist nett. Das Live-Lock-Beispiel ist jedoch nicht gut. Ob ein Thread für einen Wert blockiert ist oder nach einer Wertänderung fragt, ist konzeptionell nicht anders. Eine einfache Änderung, um eine Live-Sperre besser zu veranschaulichen, besteht darin, dass die Threads A und B die Sperren aufheben, wenn sie feststellen, dass sie nicht die zweite Sperre erhalten können, die sie benötigen. Dann schlafen sie jeweils eine Sekunde, holen sich das Schloss, das sie ursprünglich hatten, wieder, schlafen dann eine weitere Sekunde und versuchen, das andere Schloss erneut zu erwerben. Der Zyklus für jeden wäre also: 1) Erwerb-Mine, 2) Schlaf, 3) Versuch, andere zu erwerben und zu scheitern, 4) Freigabe-Mine, 5) Schlaf, 6) Wiederholung.
CognizantApe
1
Ich bezweifle, dass die Live-Locks, an die Sie denken, wirklich lange genug existieren, um Probleme zu verursachen. Wenn Sie immer alle Sperren aufgeben, die Sie halten, wenn Sie die nächste Sperre nicht zuweisen können, fehlt die Deadlock- (und Live-Lock-) Bedingung "Halten und Warten", da tatsächlich keine Wartezeit mehr besteht. ( en.wikipedia.org/wiki/Deadlock )
mmirwaldt
In der Tat fehlt der Zustand der toten Sperre, da es sich um aktive Sperren handelt, die wir diskutieren. Das Beispiel, das ich gegeben habe, ähnelt dem Beispiel für den Standardflur: geeksforgeeks.org/deadlock-starvation-and-livelock , en.wikibooks.org/wiki/Operating_System_Design/Concurrency/… , docs.oracle.com/javase/tutorial/essential / concurrency /…
CognizantApe
0

Stellen Sie sich vor, Sie haben Thread A und Thread B. Sie befinden sich beide synchronisedim selben Objekt und in diesem Block befindet sich eine globale Variable, die beide aktualisiert werden.

static boolean commonVar = false;
Object lock = new Object;

...

void threadAMethod(){
    ...
    while(commonVar == false){
         synchornized(lock){
              ...
              commonVar = true
         }
    }
}

void threadBMethod(){
    ...
    while(commonVar == true){
         synchornized(lock){
              ...
              commonVar = false
         }
    }
}

Wenn also Thread A in die whileSchleife eintritt und die Sperre hält, tut er das, was er tun muss, und setzt commonVarauf true. Dann Thread B kommt, tritt in der whileSchleife , und da commonVarist truejetzt ist es in der Lage , das Schloss zu halten. Dies geschieht, führt den synchronisedBlock aus und setzt commonVarzurück zu false. Jetzt erhält Thread A wieder sein neues CPU-Fenster. Er wollte gerade die whileSchleife verlassen, aber Thread B hat es gerade zurückgesetzt false, sodass sich der Zyklus erneut wiederholt. Threads tun etwas (damit sie nicht im herkömmlichen Sinne blockiert werden), aber für so ziemlich nichts.

Es ist vielleicht auch schön zu erwähnen, dass Vieh nicht unbedingt hier erscheinen muss. Ich gehe davon aus, dass der Scheduler den anderen Thread bevorzugt, sobald die synchronisedAusführung des Blocks abgeschlossen ist. Die meiste Zeit denke ich, dass es eine schwer zu treffende Erwartung ist und von vielen Dingen abhängt, die unter der Haube passieren.

stdout
quelle
Schönes Beispiel. Es zeigt auch, warum Sie in einem gleichzeitigen Kontext immer atomar lesen und schreiben sollten. Wenn sich die while-Schleifen innerhalb der Synchronisationsblöcke befinden würden, wäre das Obige kein Problem.
CognizantApe